diff --git a/language/codegen/coderepresentation.cpp b/language/codegen/coderepresentation.cpp index b3aefa7870..05f3d29683 100644 --- a/language/codegen/coderepresentation.cpp +++ b/language/codegen/coderepresentation.cpp @@ -1,349 +1,367 @@ /* Copyright 2008 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 "coderepresentation.h" #include #include #include #include #include #include #include namespace KDevelop { static bool onDiskChangesForbidden = false; QString CodeRepresentation::rangeText(KTextEditor::Range range) const { Q_ASSERT(range.end().line() < lines()); //Easier for single line ranges which should happen most of the time if(range.onSingleLine()) return QString( line( range.start().line() ).mid( range.start().column(), range.columnWidth() ) ); //Add up al the requested lines QString rangedText = line(range.start().line()).mid(range.start().column()); for(int i = range.start().line() + 1; i <= range.end().line(); ++i) rangedText += '\n' + ((i == range.end().line()) ? line(i).left(range.end().column()) : line(i)); return rangedText; } static void grepLine(const QString& identifier, const QString& lineText, int lineNumber, QVector& ret, bool surroundedByBoundary) { + if (identifier.isEmpty()) + return; + int pos = 0; while(true) { pos = lineText.indexOf(identifier, pos); if(pos == -1) break; int start = pos; pos += identifier.length(); int end = pos; if(!surroundedByBoundary || ( (end == lineText.length() || !lineText[end].isLetterOrNumber() || lineText[end] != '_') && (start-1 < 0 || !lineText[start-1].isLetterOrNumber() || lineText[start-1] != '_')) ) { ret << SimpleRange(lineNumber, start, lineNumber, end); } } } class EditorCodeRepresentation : public DynamicCodeRepresentation { public: EditorCodeRepresentation(KTextEditor::Document* document) : m_document(document) { m_url = IndexedString(m_document->url()); } - virtual QVector< SimpleRange > grep ( QString identifier, bool surroundedByBoundary ) const { + virtual QVector< SimpleRange > grep ( const QString& identifier, bool surroundedByBoundary ) const { QVector< SimpleRange > ret; + + if (identifier.isEmpty()) + return ret; + for(int line = 0; line < m_document->lines(); ++line) grepLine(identifier, m_document->line(line), line, ret, surroundedByBoundary); + return ret; } QString line(int line) const { if(line < 0 || line >= m_document->lines()) return QString(); return m_document->line(line); } virtual int lines() const { return m_document->lines(); } QString text() const { return m_document->text(); } bool setText(QString text) { bool ret = m_document->setText(text); ModificationRevision::clearModificationCache(m_url); return ret; } bool fileExists(){ return QFile(m_document->url().path()).exists(); } void startEdit() { m_document->startEditing(); } void endEdit() { m_document->endEditing(); } bool replace(const KTextEditor::Range& range, QString oldText, QString newText, bool ignoreOldText) { QString old = m_document->text(range); if(oldText != old && !ignoreOldText) { return false; } bool ret = m_document->replaceText(range, newText); ModificationRevision::clearModificationCache(m_url); return ret; } virtual QString rangeText(KTextEditor::Range range) const { return m_document->text(range); } private: KTextEditor::Document* m_document; IndexedString m_url; }; class FileCodeRepresentation : public CodeRepresentation { public: FileCodeRepresentation(IndexedString document) : m_document(document) { QString localFile(document.toUrl().toLocalFile()); QFile file( localFile ); if ( file.open(QIODevice::ReadOnly) ) { data = QString::fromLocal8Bit(file.readAll()); lineData = data.split('\n'); } m_exists = file.exists(); } QString line(int line) const { if(line < 0 || line >= lineData.size()) return QString(); return lineData.at(line); } - virtual QVector< SimpleRange > grep ( QString identifier, bool surroundedByBoundary ) const { + virtual QVector< SimpleRange > grep ( const QString& identifier, bool surroundedByBoundary ) const { QVector< SimpleRange > ret; + + if (identifier.isEmpty()) + return ret; + for(int line = 0; line < lineData.count(); ++line) grepLine(identifier, lineData.at(line), line, ret, surroundedByBoundary); + return ret; } virtual int lines() const { return lineData.count(); } QString text() const { return data; } bool setText(QString text) { Q_ASSERT(!onDiskChangesForbidden); QString localFile(m_document.toUrl().toLocalFile()); QFile file( localFile ); if ( file.open(QIODevice::WriteOnly) ) { QByteArray data = text.toLocal8Bit(); if(file.write(data) == data.size()) { ModificationRevision::clearModificationCache(m_document); return true; } } return false; } bool fileExists(){ return m_exists; } private: //We use QByteArray, because the column-numbers are measured in utf-8 IndexedString m_document; bool m_exists; QStringList lineData; QString data; }; class ArtificialStringData : public QSharedData { public: ArtificialStringData(QString data) { setData(data); } void setData(QString data) { m_data = data; m_lineData = m_data.split('\n'); } QString data() const { return m_data; } const QStringList& lines() const { return m_lineData; } private: QString m_data; QStringList m_lineData; }; class StringCodeRepresentation : public CodeRepresentation { public: StringCodeRepresentation(KSharedPtr _data) : data(_data) { Q_ASSERT(data); } QString line(int line) const { if(line < 0 || line >= data->lines().size()) return QString(); return data->lines().at(line); } virtual int lines() const { return data->lines().count(); } QString text() const { return data->data(); } bool setText(QString text) { data->setData(text); return true; } bool fileExists(){ return false; } - virtual QVector< SimpleRange > grep ( QString identifier, bool surroundedByBoundary ) const { + virtual QVector< SimpleRange > grep ( const QString& identifier, bool surroundedByBoundary ) const { QVector< SimpleRange > ret; + + if (identifier.isEmpty()) + return ret; + for(int line = 0; line < data->lines().count(); ++line) grepLine(identifier, data->lines().at(line), line, ret, surroundedByBoundary); + return ret; } private: KSharedPtr data; }; static QHash > artificialStrings; //Return the representation for the given URL if it exists, or an empty pointer otherwise KSharedPtr representationForUrl(IndexedString url) { if(artificialStrings.contains(url)) return artificialStrings[url]; else { IndexedString constructedUrl(CodeRepresentation::artificialUrl(url.str())); if(artificialStrings.contains(constructedUrl)) return artificialStrings[constructedUrl]; else return KSharedPtr(); } } bool artificialCodeRepresentationExists(IndexedString url) { return !representationForUrl(url).isNull(); } CodeRepresentation::Ptr createCodeRepresentation(IndexedString url) { if(artificialCodeRepresentationExists(url)) return CodeRepresentation::Ptr(new StringCodeRepresentation(representationForUrl(url))); IDocument* document = ICore::self()->documentController()->documentForUrl(url.toUrl()); if(document && document->textDocument()) return CodeRepresentation::Ptr(new EditorCodeRepresentation(document->textDocument())); else return CodeRepresentation::Ptr(new FileCodeRepresentation(url)); } void CodeRepresentation::setDiskChangesForbidden(bool changesForbidden) { onDiskChangesForbidden = changesForbidden; } KUrl CodeRepresentation::artificialUrl(const QString & name) { KUrl url(name); url.setScheme("artificial"); url.cleanPath(); return url; } InsertArtificialCodeRepresentation::InsertArtificialCodeRepresentation(IndexedString file, QString text) : m_file(file) { if(m_file.toUrl().isRelative()) { m_file = IndexedString(CodeRepresentation::artificialUrl(file.str())); int idx = 0; while(artificialStrings.contains(m_file)) { ++idx; m_file = IndexedString(CodeRepresentation::artificialUrl(QString("%1_%2").arg(idx).arg(file.str()))); } } Q_ASSERT(!artificialStrings.contains(m_file)); artificialStrings.insert(m_file, KSharedPtr(new ArtificialStringData(text))); } IndexedString InsertArtificialCodeRepresentation::file() { return m_file; } InsertArtificialCodeRepresentation::~InsertArtificialCodeRepresentation() { Q_ASSERT(artificialStrings.contains(m_file)); artificialStrings.remove(m_file); } void InsertArtificialCodeRepresentation::setText(QString text) { Q_ASSERT(artificialStrings.contains(m_file)); artificialStrings[m_file]->setData(text); } QString InsertArtificialCodeRepresentation::text() { Q_ASSERT(artificialStrings.contains(m_file)); return artificialStrings[m_file]->data(); } } diff --git a/language/codegen/coderepresentation.h b/language/codegen/coderepresentation.h index 2f174f03ce..450cf25b05 100644 --- a/language/codegen/coderepresentation.h +++ b/language/codegen/coderepresentation.h @@ -1,137 +1,137 @@ /* Copyright 2008 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 CODEREPRESENTATION_H #define CODEREPRESENTATION_H #include "../languageexport.h" #include #include class QString; namespace KTextEditor { class Range; } namespace KDevelop { class SimpleRange; class IndexedString; /** * Allows getting code-lines conveniently, either through an open editor, or from a disk-loaded file. */ class KDEVPLATFORMLANGUAGE_EXPORT CodeRepresentation : public QSharedData { public: virtual ~CodeRepresentation() { } virtual QString line(int line) const = 0; virtual int lines() const = 0; virtual QString text() const = 0; virtual QString rangeText(KTextEditor::Range range) const; /** * Search for the given identifier in the document, and returns all ranges * where it was found. * @param identifier The identifier to search for * @param surroundedByBoundary Whether only matches that are surrounded by typical word-boundaries * should be acceded. Everything except letters, numbers, and the _ character * counts as word boundary. * */ - virtual QVector grep(QString identifier, bool surroundedByBoundary = true) const = 0; + virtual QVector grep(const QString& identifier, bool surroundedByBoundary = true) const = 0; /** * Overwrites the text in the file with the new given one * * @return true on success */ virtual bool setText(QString) = 0; /** @return true if this representation represents an actual file on disk */ virtual bool fileExists() = 0; /** * Can be used for example from tests to disallow on-disk changes. When such a change is done, an assertion is triggered. * You should enable this within tests, unless you really want to work on the disk. */ static void setDiskChangesForbidden(bool changesForbidden); /** * Returns the specified name as a url for aritificial source code * suitable for code being inserted into the parser */ static KUrl artificialUrl(const QString & name); typedef KSharedPtr Ptr; }; class KDEVPLATFORMLANGUAGE_EXPORT DynamicCodeRepresentation : public CodeRepresentation { public: /** Used to group edit-history together. Call this before a bunch of replace(), and endEdit in the end. */ virtual void startEdit() = 0; virtual bool replace(const KTextEditor::Range& range, QString oldText, QString newText, bool ignoreOldText = false) = 0; /** Must be called exactly once per startEdit() */ virtual void endEdit() = 0; typedef KSharedPtr Ptr; }; /** * Creates a code-representation for the given url, that allows conveniently accessing its data. Returns zero on failure. */ KDEVPLATFORMLANGUAGE_EXPORT CodeRepresentation::Ptr createCodeRepresentation(IndexedString url); /** * @return true if an artificial code representation already exists for the specified URL */ KDEVPLATFORMLANGUAGE_EXPORT bool artificialCodeRepresentationExists(IndexedString url); /** * Allows inserting artificial source-code into the code-representation and parsing framework. * The source-code logically represents a file. * * The artificial code is automatically removed when the object is destroyed. */ class KDEVPLATFORMLANGUAGE_EXPORT InsertArtificialCodeRepresentation : public QSharedData { public: /** * Inserts an artifial source-code representation with filename @p file and the contents @p text * If @p file is not an absolute path or url, it will be made absolute using the CodeRepresentation::artifialUrl() * function, while ensuring that the name is unique. */ InsertArtificialCodeRepresentation(IndexedString file, QString text); ~InsertArtificialCodeRepresentation(); void setText(QString text); QString text(); /** * Returns the file-name for this code-representation. */ IndexedString file(); private: InsertArtificialCodeRepresentation(const InsertArtificialCodeRepresentation&); InsertArtificialCodeRepresentation& operator=(const InsertArtificialCodeRepresentation&); IndexedString m_file; }; typedef KSharedPtr InsertArtificialCodeRepresentationPointer; } #endif diff --git a/language/duchain/declaration.h b/language/duchain/declaration.h index 72695285d8..fbee58c8ee 100644 --- a/language/duchain/declaration.h +++ b/language/duchain/declaration.h @@ -1,496 +1,495 @@ /* This file is part of KDevelop Copyright 2006 Hamish Rodda Copyright 2007-2008 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 DECLARATION_H #define DECLARATION_H #include #include //#include #include "identifier.h" #include "indexedstring.h" #include "types/abstracttype.h" #include "duchainbase.h" #include "topducontext.h" #include "indexeditems.h" class QByteArray; namespace KDevelop { class AbstractType; class DUContext; class Use; class ForwardDeclaration; class DeclarationData; class DeclarationId; class Declaration; class IndexedTopDUContext; /** * \short Represents a single declaration in a definition-use chain. * * \note A du-context can be freely edited as long as it's parent-context is zero. * In the moment the parent-context is set, the context may only be edited when it * is allowed to edited it's top-level context (@see TopLevelContext::inDUChain()) */ class KDEVPLATFORMLANGUAGE_EXPORT Declaration : public DUChainBase { public: /// Access types enum AccessPolicy { Public /**< a public declaration */, Protected /**< a protected declaration */, Private /**< a private declaration */, DefaultAccess /** TypePtr type() const { return TypePtr::dynamicCast(abstractType()); } /** * Access this declaration's type. * * \note You should not compare or permanently store instances of AbstractType::Ptr. Use IndexedType instead. * \returns this declaration's type, or null if none has been assigned. */ AbstractType::Ptr abstractType() const; /** * Set this declaration's type. * \param type the type to assign. */ template void setType(TypePtr type) { setAbstractType(AbstractType::Ptr::staticCast(type)); } /** * Set this declaration's \a type. * * \param type this declaration's new type. */ virtual void setAbstractType(AbstractType::Ptr type); /** * Return an indexed form of this declaration's type. * Should be preferred, this is the fastest way, and the correct way for doing equality-comparsion. * * \returns the declaration's type. */ IndexedType indexedType() const; /** * Set this declaration's \a identifier. * * \param identifier this declaration's new identifier */ void setIdentifier(const Identifier& identifier); /** * Access this declaration's \a identifier. * * \returns this declaration's identifier. */ Identifier identifier() const; /** * Access this declaration's \a identifier. * * \return this declaration's identifier in indexed form. This is faster than identifier(), because it * equals the internal representation. Use this for example to do equality-comparison. */ const IndexedIdentifier& indexedIdentifier() const; /** * Determine the global qualified identifier of this declaration. * * \note This function is expensive, equalQualifiedIdentifier() is preferred if you * just want to compare equality. */ QualifiedIdentifier qualifiedIdentifier() const; /** * Compares the qualified identifier of this declaration with the other one, without needing to compute it. * This is more efficient than comparing the results of qualifiedIdentifier(). * * \param rhs declaration to compare identifiers with * \returns true if the identifiers are equal, otherwise false. */ bool equalQualifiedIdentifier(const Declaration* rhs) const; /** * Returns the kind of this declaration. @see Kind * */ Kind kind() const; /** * Set the kind. * * \param kind new kind * */ void setKind(Kind kind); /** * Returns the comment associated to this declaration in the source-code, or an invalid string if there is none. * Stored in utf-8 encoding. * */ QByteArray comment() const; /** * Sets the comment for this declaration. Should be utf-8 encoded. * */ void setComment(const QByteArray& str); /// Sets the comment for this declaration. void setComment(const QString& str); /** * Access whether this declaration is in the symbol table. * * \returns true if this declaration is in the symbol table, otherwise false. */ bool inSymbolTable() const; /** * Adds or removes this declaration to/from the symbol table. * * \param inSymbolTable true to add this declaration to the symbol table, false to remove it. */ virtual void setInSymbolTable(bool inSymbolTable); /** * Equivalence operator. * \param other Other declaration to compare. * \returns true if the declarations are equal, otherwise false. */ bool operator==(const Declaration& other) const; /** * Determine this declaration as a string. \returns this declaration as a string. */ virtual QString toString() const; /** * Returns a list of pairs: * An url of a file, paired together with all use-ranges of this declaration in that file. * The ranges are in the documents local revision (use DUChainUtils::transformFromRevision or usesCurrentRevision()) * The uses are unique, no 2 uses are returend that have the same range within the same file. * * This is a non-trivial operation. * */ QMap > uses() const; /** - * Determines weather the declaration has any uses or not. + * Determines whether the declaration has any uses or not. * Cheaper than calling uses(). * */ bool hasUses() const; /** * Returns a list of pairs: * An url of a file, paired together with all use-ranges of this declaration in that file. * The ranges are in the most current document revisions available. * The uses are unique, no 2 uses are returend that have the same range within the same file. * * @warning This must be called only from within the foreground, or with the foreground lock locked. * This is a non-trivial operation. * */ QMap > usesCurrentRevision() const; /** * This hash-value should differentiate between multiple different * declarations that have the same qualifiedIdentifier, but should have a different * identity, and thus own Definitions and own Uses assigned. * * Affected by function-arguments, whether this is a template-declaration, etc.. * */ virtual uint additionalIdentity() const; /** * * */ virtual IndexedInstantiationInformation specialization() const; /** * @see DeclarationId * @param forceDirect When this is true, the DeclarationId is force to be direct, and can be resolved without a symbol-table and top-context. * The same goes for Declarations that have @c alwaysForceDirect() set to true. * */ virtual DeclarationId id(bool forceDirect = false) const; /** * Returns an index that uniquely identifies this declaration within its surrounding top-context. That index can be passed * to TopDUContext::declarationFromIndex(index) to get the declaration. * This is only valid when the declaration is not a specialization (specialization() returns 0), and if it is not anonymous in its context. * * \note for this to be valid, allocateOwnIndex() must have been called first. * \note the highest big of the index is always zero! * \returns the index of the declaration within its TopDUContext. */ uint ownIndex() const; ///Whether this declaration has been inserted anonymously into its parent-context bool isAnonymous() const; /** * Clear the index for this declaration in the top context that was allocated with allocateOwnIndex(). */ void clearOwnIndex(); /** * Create an index to this declaration from the topContext(). Needed to be able to retrieve ownIndex(). */ void allocateOwnIndex(); /** * Returns a clone of this declaration, with the difference that: * - context will be zero * * The declaration will not be registered anywhere, so you must care about its deletion. * * This declaration's text-range will be referenced from the clone, so the clone must not live longer than the original. * * */ Declaration* clone() const; /** * Signalized that among multiple possible specializations, this one should be used in the UI from now on. * Currently mainly used in C++ for template support. The default-implementation registers the current specialization * of this declaration to SpecializationStore if it is nonzero. */ virtual void activateSpecialization(); enum { Identity = 7 }; protected: /** * Constructor for copy constructors in subclasses. * * \param dd data to copy. * \param url document url in which this object is located. * \param range text range which this object covers. */ Declaration( DeclarationData & dd, const RangeInRevision& range ); /** * Returns true if this declaration is being currently destroyed persistently, * which means that it should eventually deregister itself from persistent storage facilities. * * Only call this from destructors. */ bool persistentlyDestroying() const; DUCHAIN_DECLARE_DATA(Declaration) private: /** * Sub-classes should implement this and should copy as much information into the clone as possible without breaking the du-chain. * Sub-classes should also implement a public copy-constructor that can be used for cloning by sub-classes. * * \note You do not have to implement this for your language if you are not going to use it(the du-chain itself does not and should not depend on it). * */ virtual Declaration* clonePrivate() const; void updateCodeModel(); void rebuildDynamicData(DUContext* parent, uint ownIndex); friend class DUContext; friend class IndexedDeclaration; friend class LocalIndexedDeclaration; friend class TopDUContextDynamicData; DUContext* m_context; TopDUContext* m_topContext; int m_indexInTopContext; }; inline uint qHash(const IndexedDeclaration& decl) { return decl.hash(); } } Q_DECLARE_METATYPE(KDevelop::IndexedDeclaration) #endif // DECLARATION_H // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/language/duchain/ducontext.h b/language/duchain/ducontext.h index 143f7ba8fe..c01ef0932f 100644 --- a/language/duchain/ducontext.h +++ b/language/duchain/ducontext.h @@ -1,851 +1,851 @@ /* This file is part of KDevelop Copyright 2006 Hamish Rodda Copyright 2007-2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DUCONTEXT_H #define DUCONTEXT_H #include #include #include #include #include #include "identifier.h" #include "duchainbase.h" #include "types/abstracttype.h" #include "duchainpointer.h" #include "declarationid.h" #include "indexeditems.h" class QWidget; namespace KDevelop { class Declaration; class DUChain; class Use; class TopDUContext; class DUContext; class DUContextData; class KDEVPLATFORMLANGUAGE_EXPORT DUChainVisitor { public: virtual void visit(DUContext* context) = 0; virtual void visit(Declaration* declaration) = 0; virtual ~DUChainVisitor(); }; ///Represents a context only by its global indices class KDEVPLATFORMLANGUAGE_EXPORT IndexedDUContext { public: IndexedDUContext(DUContext* decl); IndexedDUContext(uint topContext = 0, uint contextIndex = 0); ///Duchain must be read locked DUContext* context() const; ///Duchain must be read locked DUContext* data() const { return context(); } bool operator==(const IndexedDUContext& rhs) const { return m_topContext == rhs.m_topContext && m_contextIndex == rhs.m_contextIndex; } uint hash() const { return (m_topContext * 57 + m_contextIndex) * 29; } bool isValid() const { return !isDummy() && context() != 0; } bool operator<(const IndexedDUContext& rhs) const { Q_ASSERT(!isDummy()); return m_topContext < rhs.m_topContext || (m_topContext == rhs.m_topContext && m_contextIndex < rhs.m_contextIndex); } //Index within the top-context uint localIndex() const { if(isDummy()) return 0; return m_contextIndex; } uint topContextIndex() const { return m_topContext; } IndexedTopDUContext indexedTopContext() const; ///The following functions allow storing 2 integers in this object and marking it as a dummy, ///which makes the isValid() function always return false for this object, and use the integers ///for other purposes ///Clears the contained data void setIsDummy(bool dummy) { if(isDummy() == dummy) return; if(dummy) m_topContext = 1 << 31; else m_topContext = 0; m_contextIndex = 0; } bool isDummy() const { //We use the second highest bit to mark dummies, because the highest is used for the sign bit of stored //integers return (bool)(m_topContext & (1 << 31)); } QPair dummyData() const { Q_ASSERT(isDummy()); return qMakePair(m_topContext & (~(1<<31)), m_contextIndex); } ///Do not call this when this object is valid. The first integer loses one bit of precision. void setDummyData(QPair data) { Q_ASSERT(isDummy()); m_topContext = data.first; m_contextIndex = data.second; Q_ASSERT(!isDummy()); m_topContext |= (1 << 31); //Mark as dummy Q_ASSERT(isDummy()); Q_ASSERT(dummyData() == data); } private: uint m_topContext; uint m_contextIndex; }; ///Represents a DUContext within a TopDUContext, without storing the TopDUContext(It must be given to data()) class KDEVPLATFORMLANGUAGE_EXPORT LocalIndexedDUContext { public: LocalIndexedDUContext(DUContext* decl); LocalIndexedDUContext(uint contextIndex = 0); //Duchain must be read locked DUContext* data(TopDUContext* top) const; bool operator==(const LocalIndexedDUContext& rhs) const { return m_contextIndex == rhs.m_contextIndex; } bool isValid() const { return m_contextIndex != 0; } uint hash() const { return m_contextIndex * 29; } bool operator<(const LocalIndexedDUContext& rhs) const { return m_contextIndex < rhs.m_contextIndex; } //Index within the top-context uint localIndex() const { return m_contextIndex; } bool isLoaded(TopDUContext* top) const; private: uint m_contextIndex; }; ///This class is used to trace imports while findDeclarationsInternal. The back-tracing may be needed for correctly resolving delayed types(templates) class ImportTraceItem { public: ImportTraceItem(const DUContext* _ctx, CursorInRevision _pos = CursorInRevision::invalid()) : ctx(_ctx), position(_pos) { } ImportTraceItem() { } //The trace goes backwards. This means that for each imported context, it contains the context the new one is imported to, not the imported context. const DUContext* ctx; CursorInRevision position; }; class ImportTrace : public KDevVarLengthArray { }; template class DUChainPointer; typedef DUChainPointer DUContextPointer; /** * A single context in source code, represented as a node in a * directed acyclic graph. * * Access to context objects must be serialised by holding the * chain lock, ie. DUChain::lock(). * * NOTE: A du-context can be freely edited as long as it's parent-context is zero. * In the moment the parent-context is set, the context may only be edited when it * is allowed to edited it's top-level context(@see TopLevelContext::inDUChain() * * \todo change child relationships to a linked list within the context? */ class KDEVPLATFORMLANGUAGE_EXPORT DUContext : public DUChainBase { friend class Use; friend class Declaration; friend class DeclarationData; friend class DUContextData; friend class DUContextDynamicData; friend class Definition; friend class VisibleDeclarationIterator; public: /** * Constructor. No convenience methods, as the initialisation order is important, * * @param anonymous Whether the context should be added as an anonymous context to the parent. That way the context can never be found through any of the parent's member-functions. * * If the parent is in the symbol table and the context is not anonymous, it will also be added to the symbol table. You nead a write-lock to the DUChain then */ explicit DUContext(const RangeInRevision& range, DUContext* parent = 0, bool anonymous = false); explicit DUContext(DUContextData&); /** * Destructor. Will delete all child contexts which are defined within * the same file as this context. */ virtual ~DUContext(); enum ContextType { Global /**< A context that declares functions, namespaces or classes */, Namespace /**< A context that declares namespace members */, Class /**< A context that declares class members */, Function /**< A context that declares function-arguments */, Template /**< A context that declares template-parameters */, Enum /**< A context that contains a list of enumerators */, Helper /**< A helper context. This context is treated specially during search: * when searching within the imports of a context, and that context's parent * is a context of type DUContext::Helper, then the upwards search is continued * into that helper(The decision happens in shouldSearchInParent) */, Other /**< Represents executable code, like for example within a compound-statement */ }; enum SearchFlag { NoSearchFlags = 0 /**< Searching for everything */, InImportedParentContext = 1 /**< Internal, do not use from outside */, OnlyContainerTypes = 2 /**< Not implemented yet */, DontSearchInParent = 4 /**< IF this flag is set, findDeclarations(..) will not search for the identifier in parent-contexts(which does not include imported parent-contexts) */, NoUndefinedTemplateParams = 8 /**< For languages that support templates(like C++). If this is set, the search should fail as soon as undefined template-parameters are involved. */, DirectQualifiedLookup = 16 /**< When this flag is used, the searched qualified identifier should NOT be split up into it's components and looked up one by one. Currently only plays a role in C++ specific parts. */, NoFiltering = 32 /**< Should be set when no filtering at all is wished, not even filtering that is natural for the underlying language(For example in C++, constructors are filtered out be default) */, OnlyFunctions = 64 /**< When this is given, only function-declarations are returned. In case of C++, this also means that constructors can be retrieved, while normally they are filtered out. */, NoImportsCheck = 128 /**< With this parameter, a global search will return all matching items, from all contexts, not only from imported ones. */, NoSelfLookUp = 256 /**< With this parameter, the special-treatment during search that allows finding the context-class by its name is disabled. */, LastSearchFlag = 512 }; Q_DECLARE_FLAGS(SearchFlags, SearchFlag) ContextType type() const; void setType(ContextType type); /** * If this context was opened by a declaration or definition, this returns that item. * The returned declaration/definition will have this context set as internalContext() * */ Declaration* owner() const; /** * Sets the declaration/definition, and also updates it's internal context(they are strictly paired together) * The declaration has to be part of the same top-context. * */ void setOwner(Declaration* decl); /** * Calculate the depth of this context, from the top level context in the file. */ int depth() const; /** * Find the top context. */ virtual TopDUContext* topContext() const; /** * Visits all duchain objects in the whole duchain. Classes that hold a unique link to duchain objects * like instantiations have to pass the visitor over to those classes. * */ virtual void visit(DUChainVisitor& visitor); /** * Find the context which most specifically covers \a position. * @param includeRightBorder When this is true, contexts will also be found that have the position on their right border. * The search is recursive, so the most specific context is found. * * @warning This uses the ranges in the local revision of the document (at last parsing time). * Use DUChainBase::transformToLocalRevision to transform the cursor into that revision first. */ DUContext* findContextAt(const CursorInRevision& position, bool includeBorders = false) const; /** * Find a child declaration that has a rang that covers the given position * The search is local, not recursive. * * @warning This uses the ranges in the local revision of the document (at last parsing time). * Use DUChainBase::transformToLocalRevision to transform the cursor into that revision first. */ Declaration* findDeclarationAt(const CursorInRevision& position) const; /** * Find the context which most specifically covers \a range. * * @warning This uses the ranges in the local revision of the document (at last parsing time). * Use DUChainBase::transformToLocalRevision to transform the cursor into that revision first. */ DUContext* findContextIncluding(const RangeInRevision& range) const; /** * Calculate the fully qualified scope identifier */ QualifiedIdentifier scopeIdentifier(bool includeClasses = false) const; /** * Returns true if this context has the same scope identifier as the given one. * This is much more efficient than computing the identifiers through scopeIdentifier(..) and comparing them * */ bool equalScopeIdentifier(const DUContext* rhs) const; /** * Scope identifier, used to qualify the identifiers occurring in each context. * This is the part relative to the parent context. */ QualifiedIdentifier localScopeIdentifier() const; ///Same as localScopeIdentifier, but faster IndexedQualifiedIdentifier indexedLocalScopeIdentifier() const; /** * Scope identifier, used to qualify the identifiers occurring in each context * This must not be called once this context has children. */ void setLocalScopeIdentifier(const QualifiedIdentifier& identifier); /** * Returns whether this context is listed in the symbol table (Namespaces and classes) */ bool inSymbolTable() const; /** * Move this object into/out of the symbol table. * You need to have a duchain write lock, unless this is a TopDUContext. */ void setInSymbolTable(bool inSymbolTable); /** * Returns the immediate parent context of this context. */ DUContext* parentContext() const; /// Represents an imported parent context. struct KDEVPLATFORMLANGUAGE_EXPORT Import { ///DUChain must be read-locked when this is called Import(DUContext* context, const DUContext* importer, const CursorInRevision& position = CursorInRevision::invalid()); Import() : position(CursorInRevision::invalid()) { } Import(const DeclarationId& id, const CursorInRevision& position = CursorInRevision::invalid()); bool operator==(const Import& rhs) const { return m_context == rhs.m_context && m_declaration == rhs.m_declaration; } ///@param topContext The top-context from where to start searching. This is important to find the correct imports /// in the case of templates or similar structures. DUContext* context(const TopDUContext* topContext, bool instantiateIfRequired = true) const; //Returns the top-context index, if this import is not a specialization import. uint topContextIndex() const { return m_context.topContextIndex(); } IndexedDUContext indexedContext() const { return m_context; } ///returns true if this import is direct (Not referring to the import by its identifier, but rather directly by its index) bool isDirect() const; ///If this import is indirect, returns the imported declaration-id DeclarationId indirectDeclarationId() const { return m_declaration; } CursorInRevision position; private: //Either we store m_declaration, or m_context. That way we can resolve specialized contexts. ///@todo Compress using union DeclarationId m_declaration; IndexedDUContext m_context; }; /** * Returns the list of imported parent contexts for this context. * @warning The list may contain objects that are not valid any more(data() returns zero), @see addImportedParentContext) * @warning The import structure may contain loops if this is a TopDUContext, so be careful when traversing the tree. * Expensive. */ virtual QVector importedParentContexts() const; /** * If the given context is directly imported into this one, and * addImportedParentContext(..) was called with a valid cursor, this will return that position. * Else an invalid cursor is returned. * */ virtual CursorInRevision importPosition(const DUContext* target) const; /** * Returns true if this context imports @param origin at any depth, else false. * */ virtual bool imports(const DUContext* origin, const CursorInRevision& position = CursorInRevision::invalid()) const; /** * Adds an imported context. * * @param anonymous If this is true, the import will not be registered at the imported context. This allows du-chain contexts importing without having a write-lock. * @param position Position where the context is imported. This is mainly important in C++ with included files. * * If the context is already imported, only the position is updated. * * \note Be sure to have set the text location first, so that * the chain is sorted correctly. */ virtual void addImportedParentContext(DUContext* context, const CursorInRevision& position = CursorInRevision::invalid(), bool anonymous = false, bool temporary = false); /** * Adds an imported context, which may be indirect. * @warning This is only allowed if this context is _NOT_ a top-context * @warning When using this mechanism, this context will not be registered as importer to the other one. * @warning The given import _must_ be indirect * @return true if the import was already imported before, else false. */ bool addIndirectImport(const DUContext::Import& import); /** * Removes a child context. */ virtual void removeImportedParentContext(DUContext* context); /** * Clear all imported parent contexts. */ virtual void clearImportedParentContexts(); /** * If this is set to true, all declarations that are added to this context will also be visible in the parent-context. * They will be visible in the parent using findDeclarations(..), and findLocalDeclarations, but will not be in the list of localDeclarations(...). * */ void setPropagateDeclarations(bool propagate); bool isPropagateDeclarations() const; /** * Returns the list of contexts importing this context. * Very expensive, since the importers top-contexts need to be loaded. */ virtual QVector importers() const; /** * Cheap, because nothing needs to be loaded. */ KDevVarLengthArray indexedImporters() const; /** * Returns the list of immediate child contexts for this context. * Expensive. */ QVector childContexts() const; /** * Clears and deletes all child contexts recursively. * This will not cross file boundaries. */ void deleteChildContextsRecursively(); ///Returns true if this declaration is accessible through the du-chain, and thus cannot be edited without a du-chain write lock virtual bool inDUChain() const; /** * Retrieve the context which is specialized with the given \a specialization as seen from the given \a topContext. * * \param specialization the specialization index (see DeclarationId) * \param topContext the top context representing the perspective from which to specialize. * if @p topContext is zero, only already existing specializations are returned, and if none exists, zero is returned. * \param upDistance upwards distance in the context-structure of the * given specialization-info. This allows specializing children. * */ virtual DUContext* specialize(IndexedInstantiationInformation specialization, const TopDUContext* topContext, int upDistance = 0); /** * Searches for and returns a declaration with a given \a identifier in this context, which * is currently active at the given text \a position, with the given type \a dataType. * In fact, only items are returned that are declared BEFORE that position. * * \param identifier the identifier of the definition to search for * \param location the text position to search for * \param topContext the top-context from where a completion is triggered. This is needed so delayed types(templates in C++) can be resolved in the correct context. * \param type the type to match, or null for no type matching. * * \returns the requested declaration if one was found, otherwise null. * * \warning this may return declarations which are not in this tree, you may need to lock them too... */ QList findDeclarations(const QualifiedIdentifier& identifier, const CursorInRevision& position = CursorInRevision::invalid(), const AbstractType::Ptr& dataType = AbstractType::Ptr(), const TopDUContext* topContext = 0, SearchFlags flags = NoSearchFlags) const; /** * Searches for and returns a declaration with a given \a identifier in this context, which * is currently active at the given text \a position. * * \param identifier the identifier of the definition to search for * \param topContext the top-context from where a completion is triggered. This is needed so delayed types(templates in C++) can be resolved in the correct context. * \param location the text position to search for * * \returns the requested declaration if one was found, otherwise null. * * \warning this may return declarations which are not in this tree, you may need to lock them too... * * \overload */ QList findDeclarations(const Identifier& identifier, const CursorInRevision& position = CursorInRevision::invalid(), const TopDUContext* topContext = 0, SearchFlags flags = NoSearchFlags) const; /** * Returns the type of any \a identifier defined in this context, or * null if one is not found. * * Does not search imported parent-contexts(like base-classes). */ QList findLocalDeclarations(const Identifier& identifier, const CursorInRevision& position = CursorInRevision::invalid(), const TopDUContext* topContext = 0, const AbstractType::Ptr& dataType = AbstractType::Ptr(), SearchFlags flags = NoSearchFlags) const; /** * Clears all local declarations. Does not delete the declaration; the caller * assumes ownership. */ QVector clearLocalDeclarations(); /** * Clears all local declarations. Deletes these declarations, as the context has * ownership. */ void deleteLocalDeclarations(); /** * Returns all local declarations * @param source A source-context that is needed to instantiate template-declarations in some cases. * If it is zero, that signalizes that missing members should not be instantiated. */ virtual QVector localDeclarations(const TopDUContext* source = 0) const; /** * Searches for the most specific context for the given cursor \a position in the given \a url. * * \param location the text position to search for * \param parent the parent context to search from (this is mostly an internal detail, but if you only * want to search in a subbranch of the chain, you may specify the parent here) * * \returns the requested context if one was found, otherwise null. */ DUContext* findContext(const CursorInRevision& position, DUContext* parent = 0) const; /** * Iterates the tree to see if the provided \a context is a subcontext of this context. * \returns true if \a context is a subcontext, otherwise false. */ bool parentContextOf(DUContext* context) const; /** * Return a list of all reachable declarations for a given cursor \a position in a given \a url. * * \param location the text position to search for * \param topContext the top-context from where a completion is triggered. This is needed so delayed types(templates in C++) can be resolved in the correct context. * \param searchInParents should declarations from parent-contexts be listed? If false, only declarations from this and imported contexts will be returned. * * The returned declarations are paired together with their inheritance-depth, which is the count of steps * to into other contexts that were needed to find the declaration. Declarations reached through a namespace- or global-context * are offsetted by 1000. * * This also includes Declarations from sub-contexts that were propagated upwards using setPropagateDeclarations(true). * * \returns the requested declarations, if any were active at that location. Declarations propagated into this context(@see setPropagateDeclarations) are included. */ QList< QPair > allDeclarations(const CursorInRevision& position, const TopDUContext* topContext, bool searchInParents=true) const; /** * Delete and remove all slaves(uses, declarations, definitions, contexts) that are not in the given set */ void cleanIfNotEncountered(const QSet& encountered); /** * Used exclusively by Declaration, do not use this. * */ void changingIdentifier( Declaration* decl, const Identifier& from, const Identifier& to ); /** * Uses: * A "Use" represents any position in a document where a Declaration is used literally. * For efficiency, since there can be many many uses, they are managed efficiently by * TopDUContext and DUContext. In TopDUContext, the used declarations are registered * and assigned a "Declaration-Index" while calling TopDUContext::indexForUsedDeclaration. * From such a declaration-index, the declaration can be retrieved back by calling TopDUContext::usedDeclarationForIndex. * * The actual uses are stored within DUContext, where each use consists of a range and the declaration-index of * the used declaration. * */ /** * Return a vector of all uses which occur in this context. * To get the actual declarations, use TopDUContext::usedDeclarationForIndex(..) with the declarationIndex. */ const Use* uses() const; ///Returns the count of uses that can be accessed through uses() int usesCount() const; - /// Determines weather the given declaration has uses or not + /// Determines whether the given declaration has uses or not static bool declarationHasUses(Declaration* decl); /** * Find the use which encompasses \a position, if one exists. * @return The local index of the use, or -1 */ int findUseAt(const CursorInRevision& position) const; /** * The change must not break the ordering * */ void changeUseRange(int useIndex, const RangeInRevision& range); /** * Assigns the declaration represented by @param declarationIndex to the use with index @param useIndex * */ void setUseDeclaration(int useIndex, int declarationIndex); /** * Creates a new use of the declaration given through @param declarationIndex. * The index must be retrieved through TopDUContext::indexForUsedDeclaration(..). * @param range The range of the use * @param insertBefore A hint where in the vector of uses to insert the use. * Must be correct so the order is preserved(ordered by position), * or -1 to automatically choose the position. * * @return Local index of the created use * */ int createUse(int declarationIndex, const RangeInRevision& range, int insertBefore = -1); /** * Deletes the use number @param index . index is the position in the vector of uses, not a used declaration index. * */ void deleteUse(int index); /** * Clear and delete all uses in this context. */ virtual void deleteUses(); /** * Recursively delete all uses in this context and all its child-contexts */ virtual void deleteUsesRecursively(); /** * Can be specialized by languages to create a navigation/information-widget. * Ideally, the widget would be based on KDevelop::QuickOpenEmbeddedWidgetInterface * for user-interaction within the quickopen list. * * The returned widget will be owned by the caller. * * @param decl A member-declaration of this context the navigation-widget should be created for. Zero to create a widget for this context. * @param topContext Top-context from where the navigation-widget is triggered. In C++, this is needed to resolve forward-declarations. * @param htmlPrefix Html-formatted text that should be prepended before any information shown by this widget * @param htmlSuffux Html-formatted text that should be appended to any information shown by this widget * * Can return zero, which the default-implementation currently always does. * */ virtual QWidget* createNavigationWidget(Declaration* decl = 0, TopDUContext* topContext = 0, const QString& htmlPrefix = QString(), const QString& htmlSuffix = QString()) const; enum { Identity = 2 }; ///Represents multiple qualified identifiers in a way that is better to manipulate and allows applying namespace-aliases or -imports easily. ///A SearchItem generally represents a tree of identifiers, and represents all the qualified identifiers that can be constructed by walking ///along the tree starting at an arbitrary root-node into the depth using the "next" pointers. ///The insertion order in the hierarchy determines the order of the represented list. struct KDEVPLATFORMLANGUAGE_EXPORT SearchItem : public KShared { typedef KSharedPtr Ptr; typedef KDevVarLengthArray PtrList; ///@todo find out why this KDevVarLengthArray crashes when it's resized! ///Constructs a representation of the given @param id qualified identifier, starting at its index @param start ///@param nextItem is set as next item to the last item in the chain SearchItem(const QualifiedIdentifier& id, Ptr nextItem = Ptr(), int start = 0); ///Constructs a representation of the given @param id qualified identifier, starting at its index @param start ///@param nextItem is set as next item to the last item in the chain SearchItem(const QualifiedIdentifier& id, const PtrList& nextItems, int start = 0); SearchItem(bool explicitlyGlobal, Identifier id, const PtrList& nextItems); SearchItem(bool explicitlyGlobal, Identifier id, Ptr nextItem); bool isEmpty() const; bool hasNext() const; ///Appends the given item to every item that can be reached from this item(Not only to the end items) ///The effect to search is that the given item is searched with all prefixes contained in this earch-item prepended. ///@warning This changes all contained sub-nodes, but they can be shared with other SearchItem trees. You should not /// use this on SearchItem trees that have shared nodes with other trees. ///These functions ignore explicitly global items. void addToEachNode(Ptr item); void addToEachNode(PtrList items); ///Returns true if the given identifier matches one of the identifiers represented by this SearchItem. Does not respect the explicitlyGlobal flag bool match(const QualifiedIdentifier& id, int offset = 0) const; //Expensive QList toList(const QualifiedIdentifier& prefix=QualifiedIdentifier()) const; void addNext(Ptr other); bool isExplicitlyGlobal; Identifier identifier; PtrList next; }; ///@todo Should be protected, moved here temporarily until I have figured out why the gcc 4.1.3 fails in cppducontext.h:212, which should work (within kdevelop) /// Declaration search implementation /** * This is a more complex interface to the declaration search engine. * Always prefer findDeclarations(..) when possible. * Advantage of this interface: * - You can search multiple identifiers at one time. However, those should be aliased identifiers for one single item, because * search might stop as soon as one item is found. * - The source top-context is needed to correctly resolve template-parameters * @param position A valid position, if in doubt use textRange().end() * @param depth Depth of the search in parents. This is used to prevent endless recursions in endless import loops. * @warning position Must be valid! * @return whether the search was successful. If it is false, it had to be stopped for special reasons(like some flags) * */ typedef KDevVarLengthArray DeclarationList; virtual bool findDeclarationsInternal(const SearchItem::PtrList& identifiers, const CursorInRevision& position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* source, SearchFlags flags, uint depth ) const; ///Call this after parsing is finished. It will optimize the internal vectors to reduce memory-usage. void squeeze(); ///Returns the qualified identifier @p id with all aliases (for example namespace imports) applied ///Example: If the namespace 'Foo' is imported, and id is 'Bar', then the returned list is 'Bar' and 'Foo::Bar' QList fullyApplyAliases(KDevelop::QualifiedIdentifier id, const KDevelop::TopDUContext* source) const; protected: /** * After one scope was searched, this function is asked whether more results should be collected. Override it, for example to collect overloaded functions. * The default-implementation returns true as soon as decls is not empty. * */ virtual bool foundEnough( const DeclarationList& decls , SearchFlags flags ) const; /** * Merges definitions and their inheritance-depth up all branches of the definition-use chain into one hash. * This includes declarations propagated from sub-contexts. * @param hadUrls is used to count together all contexts that already were visited, so they are not visited again. */ virtual void mergeDeclarationsInternal(QList< QPair >& definitions, const CursorInRevision& position, QHash& hadContexts, const TopDUContext* source, bool searchInParents = true, int currentDepth = 0) const; /// Logic for calculating the fully qualified scope name QualifiedIdentifier scopeIdentifierInternal(DUContext* context) const; virtual void findLocalDeclarationsInternal( const Identifier& identifier, const CursorInRevision & position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* source, SearchFlags flags ) const; /**Applies namespace-imports and namespace-aliases and returns possible absolute identifiers that need to be searched. * @param targetIdentifiers will be filled with all identifiers that should be searched for, instead of identifier. * @param onlyImports if this is true, namespace-aliases will not be respected, but only imports. This is faster. * */ void applyAliases(const SearchItem::PtrList& identifiers, SearchItem::PtrList& targetIdentifiers, const CursorInRevision& position, bool canBeNamespace, bool onlyImports = false) const; /** * Applies the aliases that need to be applied when moving the search from this context up to the parent-context. * The default-implementation adds a set of identifiers with the own local identifier prefixed, if this is a namespace. * For C++, this is needed when searching out of a namespace, so the item can be found within that namespace in another place. * */ virtual void applyUpwardsAliases(SearchItem::PtrList& identifiers, const TopDUContext* source) const; DUContext(DUContextData& dd, const RangeInRevision& range, DUContext* parent = 0, bool anonymous = false); ///Just uses the data from the given context(doesn't copy or change anything, and the data will not be deleted on this contexts destruction) DUContext(DUContext& useDataFrom); ///Whether this context, or any of its parent contexts, has been inserted anonymously into the du-chain(@see DUContext::DUContext) bool isAnonymous() const; /** * This is called whenever the search needs to do the decision whether it should be continued in the parent context. * It is not called when the DontSearchInParent flag is set. Else this should be overridden to do language-specific logic. * The default implementation returns false if the flag InImportedParentContext is set. * */ virtual bool shouldSearchInParent(SearchFlags flags) const; private: void rebuildDynamicData(DUContext* parent, uint ownIndex); friend class TopDUContext; friend class IndexedDUContext; friend class LocalIndexedDUContext; friend class TopDUContextDynamicData; void clearDeclarationIndices(); void updateDeclarationIndices(); DUCHAIN_DECLARE_DATA(DUContext) class DUContextDynamicData* m_dynamicData; }; /** * This is the identifier that can be used to search namespace-import declarations, and should be used to store namespace-imports. * It is stored statically for performance-reasons, so it doesn't need to be constructed every time it is used. * * @see NamespaceAliasDeclaration. * */ KDEVPLATFORMLANGUAGE_EXPORT Identifier& globalImportIdentifier(); /** * This is the identifier that can be used to search namespace-alias declarations. * It is stored statically for performance-reasons, so it doesn't need to be constructed every time it is used. * * @see NamespaceAliasDeclaration. * */ KDEVPLATFORMLANGUAGE_EXPORT Identifier& globalAliasIdentifier(); /** * Collects all uses of the given @param declarationIndex * */ KDEVPLATFORMLANGUAGE_EXPORT QList allUses(DUContext* context, int declarationIndex, bool noEmptyRanges = false); } #endif // DUCONTEXT_H // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/language/duchain/navigation/abstractnavigationwidget.cpp b/language/duchain/navigation/abstractnavigationwidget.cpp index 6c7f25b901..f81b43c84f 100644 --- a/language/duchain/navigation/abstractnavigationwidget.cpp +++ b/language/duchain/navigation/abstractnavigationwidget.cpp @@ -1,316 +1,316 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "abstractnavigationwidget.h" #include #include #include #include #include #include #include "../declaration.h" #include "../ducontext.h" #include "../duchainlock.h" #include "../functiondeclaration.h" #include "../functiondefinition.h" #include "../forwarddeclaration.h" #include "../namespacealiasdeclaration.h" #include "../classfunctiondeclaration.h" #include "../classmemberdeclaration.h" #include "../topducontext.h" #include "abstractnavigationcontext.h" #include "abstractdeclarationnavigationcontext.h" #include "navigationaction.h" #include "useswidget.h" #include "../../../interfaces/icore.h" #include "../../../interfaces/idocumentcontroller.h" #include #include namespace KDevelop { AbstractNavigationWidget::AbstractNavigationWidget() : m_browser(0), m_currentWidget(0) { setPalette( QApplication::palette() ); setFocusPolicy(Qt::NoFocus); resize(100, 100); } const int maxNavigationWidgetWidth = 580; QSize AbstractNavigationWidget::sizeHint() const { if(m_browser) { updateIdealSize(); QSize ret = QSize(qMin(m_idealTextSize.width(), maxNavigationWidgetWidth), qMin(m_idealTextSize.height(), 300)); if(m_currentWidget) { ret.setHeight( ret.height() + m_currentWidget->sizeHint().height() ); if(m_currentWidget->sizeHint().width() > ret.width()) ret.setWidth(m_currentWidget->sizeHint().width()); if(ret.width() < 500) //When we embed a widget, give it some space, even if it doesn't have a large size-hint ret.setWidth(500); } return ret; } else return QWidget::sizeHint(); } void AbstractNavigationWidget::initBrowser(int height) { Q_UNUSED(height); m_browser = new KTextBrowser; // since we can embed arbitrary HTML we have to make sure it stays readable by forcing a black-white palette - m_browser->setPalette( QPalette( Qt::black, Qt::white ) ); + m_browser->viewport()->setPalette( QPalette( Qt::black, Qt::white ) ); m_browser->setOpenLinks(false); m_browser->setOpenExternalLinks(false); QVBoxLayout* layout = new QVBoxLayout; layout->addWidget(m_browser); layout->setMargin(0); setLayout(layout); connect( m_browser, SIGNAL(anchorClicked(const QUrl&)), this, SLOT(anchorClicked(const QUrl&)) ); foreach(QWidget* w, findChildren()) w->setContextMenuPolicy(Qt::NoContextMenu); } AbstractNavigationWidget::~AbstractNavigationWidget() { if(m_currentWidget) layout()->removeWidget(m_currentWidget); } void AbstractNavigationWidget::setContext(NavigationContextPointer context, int initBrows) { if(m_browser == 0) initBrowser(initBrows); if(!context) { kDebug() << "no new context created"; return; } if(context == m_context && (!context || context->alreadyComputed())) return; if (!m_startContext) m_startContext = m_context; bool wasInitial = (m_context == m_startContext); m_context = context; update(); emit contextChanged(wasInitial, m_context == m_startContext); emit sizeHintChanged(); } void AbstractNavigationWidget::updateIdealSize() const { if(m_context && !m_idealTextSize.isValid()) { QTextDocument doc; doc.setHtml(m_currentText); if(doc.idealWidth() > maxNavigationWidgetWidth) { doc.setPageSize( QSize(maxNavigationWidgetWidth, 30) ); m_idealTextSize.setWidth(maxNavigationWidgetWidth); }else{ m_idealTextSize.setWidth(doc.idealWidth()); } m_idealTextSize.setHeight(doc.size().height()); } } void AbstractNavigationWidget::update() { setUpdatesEnabled(false); Q_ASSERT( m_context ); QString html = m_context->html(); if(!html.isEmpty()) { int scrollPos = m_browser->verticalScrollBar()->value(); m_browser->setHtml( html ); m_currentText = html; m_idealTextSize = QSize(); QSize hint = sizeHint(); if(hint.height() >= m_idealTextSize.height()) { m_browser->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); }else{ m_browser->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); } m_browser->verticalScrollBar()->setValue(scrollPos); m_browser->scrollToAnchor("currentPosition"); m_browser->show(); }else{ m_browser->hide(); } if(m_currentWidget) { layout()->removeWidget(m_currentWidget); m_currentWidget->setParent(0); } m_currentWidget = m_context->widget(); m_browser->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_browser->setMaximumHeight(10000); if(m_currentWidget) { if (m_currentWidget->metaObject() ->indexOfSignal(SIGNAL(navigateDeclaration(KDevelop::IndexedDeclaration))) != -1) { connect(m_currentWidget, SIGNAL(navigateDeclaration(KDevelop::IndexedDeclaration)), this, SLOT(navigateDeclaration(KDevelop::IndexedDeclaration))); } layout()->addWidget(m_currentWidget); if(m_context->isWidgetMaximized()) { //Leave unused room to the widget m_browser->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); m_browser->setMaximumHeight(25); } } setUpdatesEnabled(true); } NavigationContextPointer AbstractNavigationWidget::context() { return m_context; } void AbstractNavigationWidget::navigateDeclaration(KDevelop::IndexedDeclaration decl) { DUChainReadLocker lock( DUChain::lock() ); setContext(m_context->accept(decl)); } void AbstractNavigationWidget::anchorClicked(const QUrl& url) { DUChainReadLocker lock( DUChain::lock() ); //We may get deleted while the call to acceptLink, so make sure we don't crash in that case QPointer thisPtr(this); NavigationContextPointer oldContext = m_context; NavigationContextPointer nextContext = m_context->acceptLink(url.toString()); if(thisPtr) setContext( nextContext ); } void AbstractNavigationWidget::keyPressEvent(QKeyEvent* event) { QWidget::keyPressEvent(event); } void AbstractNavigationWidget::executeContextAction(QString action) { DUChainReadLocker lock( DUChain::lock() ); //We may get deleted while the call to acceptLink, so make sure we don't crash in that case QPointer thisPtr(this); NavigationContextPointer oldContext = m_context; NavigationContextPointer nextContext = m_context->executeLink(action); if(thisPtr) setContext( nextContext ); } void AbstractNavigationWidget::next() { DUChainReadLocker lock( DUChain::lock() ); Q_ASSERT( m_context ); m_context->nextLink(); update(); } void AbstractNavigationWidget::previous() { DUChainReadLocker lock( DUChain::lock() ); Q_ASSERT( m_context ); m_context->previousLink(); update(); } void AbstractNavigationWidget::accept() { DUChainReadLocker lock( DUChain::lock() ); Q_ASSERT( m_context ); QPointer thisPtr(this); NavigationContextPointer oldContext = m_context; NavigationContextPointer nextContext = m_context->accept(); if(thisPtr) setContext( nextContext ); } void AbstractNavigationWidget::back() { DUChainReadLocker lock( DUChain::lock() ); QPointer thisPtr(this); NavigationContextPointer oldContext = m_context; NavigationContextPointer nextContext = m_context->back(); if(thisPtr) setContext( nextContext ); } void AbstractNavigationWidget::up() { DUChainReadLocker lock( DUChain::lock() ); m_context->up(); update(); } void AbstractNavigationWidget::down() { DUChainReadLocker lock( DUChain::lock() ); m_context->down(); update(); } void AbstractNavigationWidget::embeddedWidgetAccept() { accept(); } void AbstractNavigationWidget::embeddedWidgetDown() { down(); } void AbstractNavigationWidget::embeddedWidgetRight() { next(); } void AbstractNavigationWidget::embeddedWidgetLeft() { previous(); } void AbstractNavigationWidget::embeddedWidgetUp() { up(); } void AbstractNavigationWidget::wheelEvent(QWheelEvent* event ) { QWidget::wheelEvent(event); event->accept(); return; } } #include "abstractnavigationwidget.moc" diff --git a/language/duchain/uses.h b/language/duchain/uses.h index 2182dd240b..07acdd45a3 100644 --- a/language/duchain/uses.h +++ b/language/duchain/uses.h @@ -1,64 +1,64 @@ /* This file is part of KDevelop Copyright 2008 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 USES_H #define USES_H #include "../languageexport.h" #include "declarationid.h" namespace KDevelop { class DeclarationId; class IndexedTopDUContext; /** * Global mapping of Declaration-Ids to top-contexts, protected through DUChainLock. * * To retrieve the actual uses, query the duchain for the files. * */ class KDEVPLATFORMLANGUAGE_EXPORT Uses { public: /// Constructor. Uses(); /// Destructor. ~Uses(); /** * Adds a top-context to the users-list of the given id * */ void addUse(const DeclarationId& id, const IndexedTopDUContext& use); /** * Removes the given top-context from the list of uses * */ void removeUse(const DeclarationId& id, const IndexedTopDUContext& use); /** - * Checks weather the given DeclarationID is is used + * Checks whether the given DeclarationID is is used * */ bool hasUses(const DeclarationId& id) const; ///Gets the top-contexts of all users assigned to the declaration-id KDevVarLengthArray uses(const DeclarationId& id) const; private: class UsesPrivate* d; }; } #endif diff --git a/language/editor/persistentmovingrange.cpp b/language/editor/persistentmovingrange.cpp index ddbe9bb09d..eefa737622 100644 --- a/language/editor/persistentmovingrange.cpp +++ b/language/editor/persistentmovingrange.cpp @@ -1,76 +1,86 @@ /* Copyright 2010 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 "persistentmovingrange.h" #include #include "simplerange.h" #include "persistentmovingrangeprivate.h" #include #include #include KDevelop::PersistentMovingRange::PersistentMovingRange(const SimpleRange& range, const IndexedString& document, bool shouldExpand) : m_p(new PersistentMovingRangePrivate) { VERIFY_FOREGROUND_LOCKED; m_p->m_range = range; m_p->m_document = document; m_p->m_shouldExpand = shouldExpand; m_p->connectTracker(); } void KDevelop::PersistentMovingRange::setZDepth(float depth) const { VERIFY_FOREGROUND_LOCKED; m_p->m_zDepth = depth; if(m_p->m_movingRange) m_p->m_movingRange->setZDepth(depth); } KDevelop::PersistentMovingRange::~PersistentMovingRange() { VERIFY_FOREGROUND_LOCKED; if(m_p->m_movingRange) delete m_p->m_movingRange; delete m_p; } KDevelop::SimpleRange KDevelop::PersistentMovingRange::range() const { VERIFY_FOREGROUND_LOCKED; m_p->updateRangeFromMoving(); return m_p->m_range; } +QString KDevelop::PersistentMovingRange::text() const +{ + VERIFY_FOREGROUND_LOCKED; + + if(m_p->m_movingRange) + return m_p->m_movingRange->document()->text(m_p->m_movingRange->toRange()); + + return QString(); +} + bool KDevelop::PersistentMovingRange::valid() const { VERIFY_FOREGROUND_LOCKED; return m_p->m_valid; } void KDevelop::PersistentMovingRange::setAttribute(KTextEditor::Attribute::Ptr attribute) { VERIFY_FOREGROUND_LOCKED; if(m_p->m_movingRange) m_p->m_movingRange->setAttribute(attribute); } diff --git a/language/editor/persistentmovingrange.h b/language/editor/persistentmovingrange.h index cfabc84b6e..e131a0fd7d 100644 --- a/language/editor/persistentmovingrange.h +++ b/language/editor/persistentmovingrange.h @@ -1,88 +1,93 @@ /* Copyright 2010 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. */ #ifndef PERSISTENTMOVINGRANGE_H #define PERSISTENTMOVINGRANGE_H #include #include #include #include "documentrange.h" namespace KDevelop { struct PersistentMovingRangePrivate; /** * A range object that is automatically adapted to all changes a user does to a document. The object * also survives when the document is opened or closed, as long as the document is only edited from within * the application. * * This object must only be used from within the foreground, or with the foreground lock held. * * @todo The implementation of this object is not finished yet, the range is only persistent until the * document is closed/reloaded/cleared. * */ class KDEVPLATFORMLANGUAGE_EXPORT PersistentMovingRange : public KShared { public: typedef KSharedPtr Ptr; /** * Creates a new persistent moving range based on the current revision of the given document * */ PersistentMovingRange(const SimpleRange& range, const IndexedString& document, bool shouldExpand = false); ~PersistentMovingRange(); IndexedString document() const; /** * Returns the range in the current revision of the document */ SimpleRange range() const; /** * Changes the z-depth for highlighting (see KTextEditor::MovingRange) * */ void setZDepth(float depth) const; + /** + * Returns the text contained by the range. Currently only works when the range is open in the editor. + * */ + QString text() const; + /** * Change the highlighting attribute. * */ void setAttribute(KTextEditor::Attribute::Ptr attribute); /** * Whether this range is still valid. The range is invalidated if the document is changed externally, * as such a change can not be tracked correctly. * */ bool valid() const; private: PersistentMovingRange(const PersistentMovingRange& ); PersistentMovingRange& operator=(const PersistentMovingRange& rhs); PersistentMovingRangePrivate* m_p; }; } #endif // PERSISTENTMOVINGRANGE_H diff --git a/plugins/contextbrowser/contextbrowser.cpp b/plugins/contextbrowser/contextbrowser.cpp index 0fd80361d4..86cd7f942a 100644 --- a/plugins/contextbrowser/contextbrowser.cpp +++ b/plugins/contextbrowser/contextbrowser.cpp @@ -1,1307 +1,1307 @@ /* * 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 "browsemanager.h" ///TODO: cleanup includes #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 "contextbrowserview.h" #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( KStandardShortcut::Back ); + 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( KStandardShortcut::Forward ); + 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::ALT | Qt::Key_Up ); + 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::ALT | Qt::Key_Down ); + 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); } 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) { 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(DeclarationPointer) ), this, SLOT( declarationSelectedInUI(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 QAction(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::findUses() { QAction* action = qobject_cast(sender()); Q_ASSERT(action); DUChainReadLocker lock(DUChain::lock()); KDevelop::IndexedDeclaration decl = action->data().value(); if(!decl.data()) return; QWidget* widget = ICore::self()->uiController()->findToolView(i18n("Code Browser"), m_viewFactory, KDevelop::IUiController::CreateAndRaise); if(!widget) return; ContextBrowserView* view = dynamic_cast(widget); Q_ASSERT(view); view->allowLockedUpdate(); view->setDeclaration(decl.data(), decl.data()->topContext(), true); KDevelop::AbstractNavigationWidget* navigationWidget = dynamic_cast(view->navigationWidget()); if(navigationWidget) navigationWidget->executeContextAction("show_uses"); } 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::hideTooTip() { if(m_currentToolTip) { m_currentToolTip->deleteLater(); m_currentToolTip = 0; m_currentNavigationWidget = 0; } } 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->resize( navigationWidget->sizeHint() + QSize(10, 10) ); kDebug() << "tooltip size" << tooltip->size(); m_currentToolTip = tooltip; m_currentNavigationWidget = navigationWidget; ActiveToolTip::showToolTip(tooltip); //First disconnect to prevent multiple connections disconnect(view, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(hideTooTip())); disconnect(view, SIGNAL(focusOut(KTextEditor::View*)), this, SLOT(hideTooTip())); connect(view, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(hideTooTip())); connect(view, SIGNAL(focusOut(KTextEditor::View*)), this, SLOT(hideTooTip())); }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 // 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(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*, const KTextEditor::Cursor& ) ), this, SLOT( cursorPositionChanged( KTextEditor::View*, const KTextEditor::Cursor& ) ) ); ///Just to make sure that multiple connections don't happen connect( v, SIGNAL( cursorPositionChanged( KTextEditor::View*, const KTextEditor::Cursor& ) ), this, SLOT( cursorPositionChanged( KTextEditor::View*, const 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(const KTextEditor::Cursor&, QString&)), this, SLOT(textHintRequested(const 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; } } #include "contextbrowser.moc" // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/grepview/grepdialog.cpp b/plugins/grepview/grepdialog.cpp index c615109b40..976336138f 100644 --- a/plugins/grepview/grepdialog.cpp +++ b/plugins/grepview/grepdialog.cpp @@ -1,474 +1,475 @@ /*************************************************************************** * Copyright 1999-2001 Bernd Gehrmann and the KDevelop Team * * bernd@kdevelop.org * * Copyright 2007 Dukju Ahn * * 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. * * * ***************************************************************************/ #include "grepdialog.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 "grepviewplugin.h" #include "grepjob.h" #include "grepoutputview.h" #include "grepfindthread.h" #include "greputil.h" #include #include using namespace KDevelop; namespace { QString allOpenFilesString = i18n("All Open Files"); QString allOpenProjectsString = i18n("All Open Projects"); const QStringList template_desc = QStringList() << "verbatim" << "word" << "assignment" << "->MEMBER(" << "class::MEMBER(" << "OBJECT->member("; const QStringList template_str = QStringList() << "%s" << "\\b%s\\b" << "\\b%s\\b\\s*=[^=]" << "\\->\\s*\\b%s\\b\\s*\\(" << "([a-z0-9_$]+)\\s*::\\s*\\b%s\\b\\s*\\(" << "\\b%s\\b\\s*\\->\\s*([a-z0-9_$]+)\\s*\\("; const QStringList repl_template = QStringList() << "%s" << "%s" << "%s = " << "->%s(" << "\\1::%s(" << "%s->\\1("; const QStringList filepatterns = QStringList() << "*.h,*.hxx,*.hpp,*.hh,*.h++,*.H,*.tlh,*.cpp,*.cc,*.C,*.c++,*.cxx,*.ocl,*.inl,*.idl,*.c,*.m,*.mm,*.M,*y,*ypp,*yxx,*y++,*l" << "*.cpp,*.cc,*.C,*.c++,*.cxx,*.ocl,*.inl,*.c,*.m,*.mm,*.M" << "*.h,*.hxx,*.hpp,*.hh,*.h++,*.H,*.tlh,*.idl" << "*.adb" << "*.cs" << "*.f" << "*.html,*.htm" << "*.hs" << "*.java" << "*.js" << "*.php,*.php3,*.php4" << "*.pl" << "*.pp,*.pas" << "*.py" << "*.js,*.css,*.yml,*.rb,*.rhtml,*.html.erb,*.rjs,*.js.rjs,*.rxml,*.xml.builder" << "CMakeLists.txt,*.cmake" << "*"; const QStringList excludepatterns = QStringList() << "/CVS/,/SCCS/,/.svn/,/_darcs/,/build/,/.git/" << ""; } const KDialog::ButtonCode GrepDialog::SearchButton = KDialog::User1; GrepDialog::GrepDialog( GrepViewPlugin * plugin, QWidget *parent, bool setLastUsed ) : KDialog(parent), Ui::GrepWidget(), m_plugin( plugin ) { setAttribute(Qt::WA_DeleteOnClose); setButtons( SearchButton | KDialog::Cancel ); setButtonText( SearchButton, i18n("Search...") ); setButtonIcon( SearchButton, KIcon("edit-find") ); setCaption( i18n("Find/Replace In Files") ); setDefaultButton( SearchButton ); setupUi(mainWidget()); KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); // add default values when the most recent ones should not be set if(!setLastUsed) { patternCombo->addItem( "" ); } patternCombo->addItems( cg.readEntry("LastSearchItems", QStringList()) ); patternCombo->setInsertPolicy(QComboBox::InsertAtTop); templateTypeCombo->addItems(template_desc); templateTypeCombo->setCurrentIndex( cg.readEntry("LastUsedTemplateIndex", 0) ); templateEdit->addItems( cg.readEntry("LastUsedTemplateString", template_str) ); templateEdit->setEditable(true); templateEdit->setCompletionMode(KGlobalSettings::CompletionPopup); KCompletion* comp = templateEdit->completionObject(); connect(templateEdit, SIGNAL(returnPressed(const QString&)), comp, SLOT(addItem(const QString&))); for(int i=0; icount(); i++) comp->addItem(templateEdit->itemText(i)); replacementTemplateEdit->addItems( cg.readEntry("LastUsedReplacementTemplateString", repl_template) ); replacementTemplateEdit->setEditable(true); replacementTemplateEdit->setCompletionMode(KGlobalSettings::CompletionPopup); comp = replacementTemplateEdit->completionObject(); connect(replacementTemplateEdit, SIGNAL(returnPressed(const QString&)), comp, SLOT(addItem(const QString&))); for(int i=0; icount(); i++) comp->addItem(replacementTemplateEdit->itemText(i)); regexCheck->setChecked(cg.readEntry("regexp", false )); caseSensitiveCheck->setChecked(cg.readEntry("case_sens", true)); setDirectory( QDir::homePath() ); directoryRequester->setMode( KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly ); syncButton->setIcon(KIcon("dirsync")); syncButton->setMenu(createSyncButtonMenu()); recursiveCheck->setChecked(cg.readEntry("recursive", true)); limitToProjectCheck->setChecked(cg.readEntry("search_project_files", true)); filesCombo->addItems(cg.readEntry("file_patterns", filepatterns)); excludeCombo->addItems(cg.readEntry("exclude_patterns", excludepatterns) ); connect(this, SIGNAL(buttonClicked(KDialog::ButtonCode)), this, SLOT(performAction(KDialog::ButtonCode))); connect(templateTypeCombo, SIGNAL(activated(int)), this, SLOT(templateTypeComboActivated(int))); connect(patternCombo, SIGNAL(editTextChanged(const QString&)), this, SLOT(patternComboEditTextChanged( const QString& ))); patternComboEditTextChanged( patternCombo->currentText() ); patternCombo->setFocus(); connect(directoryRequester, SIGNAL(textChanged(const QString&)), this, SLOT(directoryChanged(const QString&))); } void GrepDialog::addUrlToMenu(QMenu* menu, KUrl url) { QAction* action = menu->addAction(m_plugin->core()->projectController()->prettyFileName(url, KDevelop::IProjectController::FormatPlain)); action->setData(QVariant(url.pathOrUrl())); connect(action, SIGNAL(triggered(bool)), SLOT(synchronizeDirActionTriggered(bool))); } void GrepDialog::addStringToMenu(QMenu* menu, QString string) { QAction* action = menu->addAction(string); action->setData(QVariant(string)); connect(action, SIGNAL(triggered(bool)), SLOT(synchronizeDirActionTriggered(bool))); } void GrepDialog::synchronizeDirActionTriggered(bool) { QAction* action = qobject_cast(sender()); Q_ASSERT(action); setDirectory(action->data().value()); } QMenu* GrepDialog::createSyncButtonMenu() { QMenu* ret = new QMenu; QAction* action = 0; QSet hadUrls; IDocument *doc = m_plugin->core()->documentController()->activeDocument(); if ( doc ) { KUrl url = doc->url(); url.cd(".."); while(m_plugin->core()->projectController()->findProjectForUrl(url)) { url.adjustPath(KUrl::RemoveTrailingSlash); if(hadUrls.contains(url)) break; hadUrls.insert(url); addUrlToMenu(ret, url); if(!url.cd("..")) break; } // if the current file's parent directory is not in the project, add it url = doc->url().upUrl(); url.adjustPath(KUrl::RemoveTrailingSlash); if(!hadUrls.contains(url)) { hadUrls.insert(url); addUrlToMenu(ret, url); } } foreach(IProject* project, m_plugin->core()->projectController()->projects()) { KUrl url = project->folder(); url.adjustPath(KUrl::RemoveTrailingSlash); if(hadUrls.contains(url)) continue; addUrlToMenu(ret, url); } addStringToMenu(ret, allOpenFilesString); addStringToMenu(ret, allOpenProjectsString); return ret; } void GrepDialog::directoryChanged(const QString& dir) { setEnableProjectBox(false); KUrl currentUrl = dir; if( !currentUrl.isValid() ) return; bool projectAvailable = true; foreach(KUrl url, getDirectoryChoice()) { IProject *proj = ICore::self()->projectController()->findProjectForUrl( currentUrl ); if( !proj || !proj->folder().isLocalFile() ) projectAvailable = false; } setEnableProjectBox(projectAvailable); } GrepDialog::~GrepDialog() { KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); // memorize the last patterns and paths cg.writeEntry("LastSearchItems", qCombo2StringList(patternCombo)); cg.writeEntry("regexp", regexCheck->isChecked()); cg.writeEntry("recursive", recursiveCheck->isChecked()); cg.writeEntry("search_project_files", limitToProjectCheck->isChecked()); cg.writeEntry("case_sens", caseSensitiveCheck->isChecked()); cg.writeEntry("exclude_patterns", qCombo2StringList(excludeCombo)); cg.writeEntry("file_patterns", qCombo2StringList(filesCombo)); cg.writeEntry("LastUsedTemplateIndex", templateTypeCombo->currentIndex()); cg.writeEntry("LastUsedTemplateString", qCombo2StringList(templateEdit)); cg.writeEntry("LastUsedReplacementTemplateString", qCombo2StringList(templateEdit)); cg.sync(); } void GrepDialog::templateTypeComboActivated(int index) { templateEdit->setCurrentItem( template_str[index], true ); replacementTemplateEdit->setCurrentItem( repl_template[index], true ); } void GrepDialog::setEnableProjectBox(bool enable) { limitToProjectCheck->setEnabled(enable); if (!enable) limitToProjectCheck->setChecked(false); } void GrepDialog::setPattern(const QString &pattern) { patternCombo->setEditText(pattern); } void GrepDialog::setDirectory(const QString &dir) { if(dir.startsWith("/")) { directoryRequester->fileDialog()->setUrl( KUrl( dir ) ); directoryRequester->completionObject()->setDir( dir ); } directoryRequester->lineEdit()->setText(dir); } QString GrepDialog::patternString() const { return patternCombo->currentText(); } QString GrepDialog::templateString() const { return templateEdit->currentText().isEmpty() ? "%s" : templateEdit->currentText(); } QString GrepDialog::replacementTemplateString() const { return replacementTemplateEdit->currentText(); } QString GrepDialog::filesString() const { return filesCombo->currentText(); } QString GrepDialog::excludeString() const { return excludeCombo->currentText(); } bool GrepDialog::useProjectFilesFlag() const { return limitToProjectCheck->isChecked(); } bool GrepDialog::regexpFlag() const { return regexCheck->isChecked(); } bool GrepDialog::recursiveFlag() const { return recursiveCheck->isChecked(); } bool GrepDialog::caseSensitiveFlag() const { return caseSensitiveCheck->isChecked(); } void GrepDialog::patternComboEditTextChanged( const QString& text) { enableButton( SearchButton, !text.isEmpty() ); } QList< KUrl > GrepDialog::getDirectoryChoice() const { QList< KUrl > ret; QString text = directoryRequester->lineEdit()->text(); if(text == allOpenFilesString) { foreach(IDocument* doc, ICore::self()->documentController()->openDocuments()) ret << doc->url(); }else if(text == allOpenProjectsString) { foreach(IProject* project, ICore::self()->projectController()->projects()) ret << project->folder(); }else{ QStringList semicolonSeparatedFileList = text.split(";"); if(!semicolonSeparatedFileList.isEmpty() && QFileInfo(semicolonSeparatedFileList[0]).exists()) { // We use QFileInfo to make sure this is really a semicolon-separated file list, not a file containing // a semicolon in the name. foreach(QString file, semicolonSeparatedFileList) ret << KUrl::fromPath(file); }else{ ret << directoryRequester->url(); } } return ret; } bool GrepDialog::isPartOfChoice(KUrl url) const { foreach(KUrl choice, getDirectoryChoice()) if(choice.isParentOf(url) || choice.equals(url)) return true; return false; } void GrepDialog::start() { performAction(SearchButton); } void GrepDialog::performAction(KDialog::ButtonCode button) { // a click on cancel trigger this signal too if( button != SearchButton ) return; // search for unsaved documents QList unsavedFiles; QStringList include = GrepFindFilesThread::parseInclude(filesString()); QStringList exclude = GrepFindFilesThread::parseExclude(excludeString()); foreach(IDocument* doc, ICore::self()->documentController()->openDocuments()) { KUrl docUrl = doc->url(); if(doc->state() != IDocument::Clean && isPartOfChoice(docUrl) && QDir::match(include, docUrl.fileName()) && !QDir::match(exclude, docUrl.toLocalFile())) { unsavedFiles << doc; } } if(!ICore::self()->documentController()->saveSomeDocuments(unsavedFiles)) { close(); return; } QList choice = getDirectoryChoice(); GrepJob* job = m_plugin->newGrepJob(); QString descriptionOrUrl(directoryRequester->lineEdit()->text()); QString description = descriptionOrUrl; // Shorten the description if(descriptionOrUrl != allOpenFilesString && descriptionOrUrl != allOpenProjectsString && choice.size() > 1) description = i18n("%1, and %2 more items", choice[0].pathOrUrl(), choice.size()-1); GrepOutputViewFactory *m_factory = new GrepOutputViewFactory(); GrepOutputView *toolView = (GrepOutputView*)ICore::self()->uiController()-> findToolView(i18n("Find/Replace in Files"), m_factory, IUiController::CreateAndRaise); GrepOutputModel* outputModel = toolView->renewModel(patternString(), description); toolView->setPlugin(m_plugin); connect(job, SIGNAL(showErrorMessage(QString, int)), toolView, SLOT(showErrorMessage(QString))); //the GrepOutputModel gets the 'showMessage' signal to store it and forward //it to toolView connect(job, SIGNAL(showMessage(KDevelop::IStatus*, QString, int)), outputModel, SLOT(showMessageSlot(KDevelop::IStatus*, QString))); connect(outputModel, SIGNAL(showMessage(KDevelop::IStatus*,QString)), toolView, SLOT(showMessage(KDevelop::IStatus*,QString))); connect(toolView, SIGNAL(outputViewIsClosed()), job, SLOT(kill())); job->setOutputModel(outputModel); job->setPatternString(patternString()); job->setReplacementTemplateString(replacementTemplateString()); job->setTemplateString(templateString()); job->setFilesString(filesString()); job->setExcludeString(excludeString()); job->setDirectoryChoice(choice); job->setProjectFilesFlag( useProjectFilesFlag() ); job->setRegexpFlag( regexpFlag() ); job->setRecursive( recursiveFlag() ); job->setCaseSensitive( caseSensitiveFlag() ); ICore::self()->runController()->registerJob(job); m_plugin->rememberSearchDirectory(descriptionOrUrl); close(); } #include "grepdialog.moc" diff --git a/plugins/grepview/grepwidget.ui b/plugins/grepview/grepwidget.ui index 38571402d7..b39c026b08 100644 --- a/plugins/grepview/grepwidget.ui +++ b/plugins/grepview/grepwidget.ui @@ -1,447 +1,442 @@ GrepWidget 0 0 730 330 0 0 730 0 16777215 16777215 Find-Replace In Files Pattern: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter patternCombo 0 0 Enter the regular expression you want to search for here. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Enter the regular expression you want to search for here.</p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">If you do not check "Regular Expression" below, this is considered a raw string. That means, all meta characters are escaped.</p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Possible meta characters are:</p> <ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">.</span> - Matches any character</li> <li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">^</span> - Matches the beginning of a line</li> <li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">$</span> - Matches the end of a line</li> <li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">\b</span> - Matches a word boundary</li> <li style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">\s</span> - Matches any whitespace character</li></ul> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The following repetition operators exist:</p> <ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">?</span> - The preceding item is matched at most once</li> <li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">*</span> - The preceding item is matched zero or more times</li> <li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">+</span> - The preceding item is matched one or more times</li> <li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">{</span><span style=" font-weight:600; font-style:italic;">n</span><span style=" font-weight:600;">}</span> - The preceding item is matched exactly <span style=" font-style:italic;">n</span> times</li> <li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">{</span><span style=" font-weight:600; font-style:italic;">n</span><span style=" font-weight:600;">,}</span> - The preceding item is matched <span style=" font-style:italic;">n</span> or more times</li> <li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">{,</span><span style=" font-weight:600; font-style:italic;">n</span><span style=" font-weight:600;">}</span> - The preceding item is matched at most <span style=" font-style:italic;">n</span> times</li> <li style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">{</span><span style=" font-weight:600; font-style:italic;">n</span><span style=" font-weight:600;">,</span><span style=" font-weight:600; font-style:italic;">m</span><span style=" font-weight:600;">}</span> - The preceding item is matched at least <span style=" font-style:italic;">n</span>, but at most <span style=" font-style:italic;">m</span> times.</li></ul> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Furthermore, backreferences to bracketed subexpressions are available via the notation \<span style=" font-style:italic;">n</span>.</p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">For further reference, look at <a href="http://www.pcre.org"><span style=" text-decoration: underline; color:#0057ae;">www.pcre.org</span></a> or <span style=" font-style:italic;">man pcresyntax.</span></p></body></html> true false Template: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter templateEdit This is the regular expression template. This is the regular expression template. <i>%s</i> will be replaced by the pattern, while <i>%%</i> will be replaced by <i>%</i>. Regular Expression: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter regexCheck Enable or disable regular expression. true Case Sensitive: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter caseSensitiveCheck 0 0 Case-sensitive searching. true Location(s): Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter directoryRequester 0 0 Select the location where you want to search. It can be a directory, a file, or a semicolon separated list of directories/files. 0 0 Synchronize with current document directory. Recursive: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter recursiveCheck true Limit to project files: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter limitToProjectCheck 0 0 true Files: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter filesCombo Files filter. - Enter the file name pattern of the files to search here. You may give several patterns separated by spaces. + Enter the file name pattern of the files to search here. You may give several patterns separated by commas or spaces. true false Exclude: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter excludeCombo Files pattern to exclude - Enter the file name pattern of the files to exclude from the search here. You may give several patterns separated by spaces.<p>Every pattern is internally surrounded by asterisks, so that each pattern can match parts of the file paths.</p> + Enter the file name pattern of the files to exclude from the search here. You may give several patterns separated by commas or spaces.<p>Every pattern is internally surrounded by asterisks, so that each pattern can match parts of the file paths.</p> true false Qt::Horizontal QSizePolicy::Fixed 141 16 Qt::Horizontal QSizePolicy::Fixed 41 20 Qt::Horizontal QSizePolicy::Fixed 151 31 Qt::Horizontal QSizePolicy::Fixed 361 20 Enter the replacement template. Replacement Template: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter replacementTemplateEdit KUrlRequester QFrame
kurlrequester.h
KPushButton QPushButton
kpushbutton.h
- - KLineEdit - QLineEdit -
klineedit.h
-
KComboBox QComboBox
kcombobox.h
patternCombo templateEdit templateTypeCombo regexCheck caseSensitiveCheck directoryRequester syncButton recursiveCheck limitToProjectCheck filesCombo excludeCombo
diff --git a/shell/workingsets/workingset.cpp b/shell/workingsets/workingset.cpp index c494e5146c..3ad8c7699a 100644 --- a/shell/workingsets/workingset.cpp +++ b/shell/workingsets/workingset.cpp @@ -1,528 +1,527 @@ /* 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 "workingset.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define SYNC_OFTEN using namespace KDevelop; bool WorkingSet::m_loading = false; WorkingSet::WorkingSet(QString id, QString icon) : m_id(id), m_iconName(icon) { //Give the working-set icons one color, so they are less disruptive QImage imgActive(KIconLoader::global()->loadIcon(icon, KIconLoader::NoGroup, 16).toImage()); QImage imgInactive = imgActive; QColor activeIconColor = QApplication::palette().color(QPalette::Active, QPalette::Highlight); QColor inActiveIconColor = QApplication::palette().color(QPalette::Active, QPalette::Base); KIconEffect::colorize(imgActive, KColorUtils::mix(inActiveIconColor, activeIconColor, 0.7), 0.5); KIconEffect::colorize(imgInactive, KColorUtils::mix(inActiveIconColor, activeIconColor, 0.3), 0.5); m_activeIcon = QIcon(QPixmap::fromImage(imgActive)); m_inactiveIcon = QIcon(QPixmap::fromImage(imgActive)); QImage imgNonPersistent = imgInactive; KIconEffect::deSaturate(imgNonPersistent, 1.0); m_inactiveNonPersistentIcon = QIcon(QPixmap::fromImage(imgNonPersistent)); //effect.apply(KIconLoader::global()->loadIcon(icon, KIconLoader::NoGroup, 16), KIconLoader::NoGroup, ); } WorkingSet::WorkingSet( const KDevelop::WorkingSet& rhs ) : QObject() { m_id = rhs.m_id + "_copy_"; } -void WorkingSet::saveFromArea(Sublime::Area* a, Sublime::AreaIndex * area, KConfigGroup & group) +void WorkingSet::saveFromArea( Sublime::Area* a, Sublime::AreaIndex * area, KConfigGroup setGroup, KConfigGroup areaGroup ) { if (area->isSplitted()) { - group.writeEntry("Orientation", area->orientation() == Qt::Horizontal ? "Horizontal" : "Vertical"); + setGroup.writeEntry("Orientation", area->orientation() == Qt::Horizontal ? "Horizontal" : "Vertical"); if (area->first()) { - KConfigGroup subgroup(&group, "0"); - subgroup.deleteGroup(); - saveFromArea(a, area->first(), subgroup); + saveFromArea(a, area->first(), KConfigGroup(&setGroup, "0"), KConfigGroup(&areaGroup, "0")); } if (area->second()) { - KConfigGroup subgroup(&group, "1"); - subgroup.deleteGroup(); - saveFromArea(a, area->second(), subgroup); + saveFromArea(a, area->second(), KConfigGroup(&setGroup, "1"), KConfigGroup(&areaGroup, "1")); } } else { - group.writeEntry("View Count", area->viewCount()); - + setGroup.writeEntry("View Count", area->viewCount()); + areaGroup.writeEntry("View Count", area->viewCount()); int index = 0; foreach (Sublime::View* view, area->views()) { - kDebug() << view->document()->title(); - group.writeEntry(QString("View %1 Type").arg(index), view->document()->documentType()); - group.writeEntry(QString("View %1").arg(index), view->document()->documentSpecifier()); - - TextDocument *textDoc = qobject_cast(view->document()); - if (textDoc && textDoc->textDocument()) { - QString encoding = textDoc->textDocument()->encoding(); - if (!encoding.isEmpty()) - group.writeEntry(QString("View %1 Encoding").arg(index), encoding); - } - QString state = view->viewState(); - if (!state.isEmpty()) - group.writeEntry(QString("View %1 State").arg(index), state); - + //The working set config gets an updated list of files + QString docSpec = view->document()->documentSpecifier(); + setGroup.writeEntry(QString("View %1").arg(index), docSpec); + setGroup.writeEntry(QString("View %1 Type").arg(index), view->document()->documentType()); + //The area specific config stores the working set documents in order along with their state + areaGroup.writeEntry(QString("View %1").arg(index), docSpec); + areaGroup.writeEntry(QString("View %1 State").arg(index), view->viewState()); ++index; } } } bool WorkingSet::isEmpty() const { KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); return !group.hasKey("Orientation") && group.readEntry("View Count", 0) == 0; } struct DisableMainWindowUpdatesFromArea { DisableMainWindowUpdatesFromArea(Sublime::Area* area) : m_area(area) { if(area) { foreach(Sublime::MainWindow* window, Core::self()->uiControllerInternal()->mainWindows()) { if(window->area() == area) { if(window->updatesEnabled()) { wasUpdatesEnabled.insert(window); window->setUpdatesEnabled(false); } } } } } ~DisableMainWindowUpdatesFromArea() { if(m_area) { foreach(Sublime::MainWindow* window, wasUpdatesEnabled) { window->setUpdatesEnabled(true); } } } Sublime::Area* m_area; QSet wasUpdatesEnabled; }; void loadFileList(QStringList& ret, KConfigGroup group) { if (group.hasKey("Orientation")) { QStringList subgroups = group.groupList(); if (subgroups.contains("0")) { { KConfigGroup subgroup(&group, "0"); loadFileList(ret, subgroup); } if (subgroups.contains("1")) { KConfigGroup subgroup(&group, "1"); loadFileList(ret, subgroup); } } } else { int viewCount = group.readEntry("View Count", 0); for (int i = 0; i < viewCount; ++i) { QString type = group.readEntry(QString("View %1 Type").arg(i), ""); QString specifier = group.readEntry(QString("View %1").arg(i), ""); ret << specifier; } } } QStringList WorkingSet::fileList() const { QStringList ret; KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); loadFileList(ret, group); return ret; } void WorkingSet::loadToArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex, bool clear) { PushValue enableLoading(m_loading, true); DisableMainWindowUpdatesFromArea updatesDisabler(area); kDebug() << "loading working-set" << m_id << "into area" << area; if(clear) { kDebug() << "clearing area with working-set" << area->workingSet(); QSet< QString > files = fileList().toSet(); foreach(Sublime::View* view, area->views()) { Sublime::UrlDocument* doc = dynamic_cast(view->document()); if(!doc || !files.contains(doc->documentSpecifier())) area->closeView(view); } } KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); - KConfigGroup group = setConfig.group(m_id); + KConfigGroup setGroup = setConfig.group(m_id); + KConfigGroup areaGroup = setConfig.group(m_id + '|' + area->title()); - loadToArea(area, areaIndex, group); + loadToArea(area, areaIndex, setGroup, areaGroup); //activate view in the working set - if (!area->views().isEmpty()) { - foreach(Sublime::MainWindow* window, Core::self()->uiControllerInternal()->mainWindows()) { - if(window->area() == area) { - QString activeView = group.readEntry("Active View", QString()); - kDebug() << activeView; - bool found = false; - foreach (Sublime::View *v, area->views()) { - if (v->document()->documentSpecifier() == activeView) { - window->activateView(v); - found = true; - break; - } - } - break; - } + QString activeView = areaGroup.readEntry("Active View", QString()); + foreach (Sublime::View *v, area->views()) { + if (v->document()->documentSpecifier() == activeView) { + area->setActiveView(v); + break; } } } -void WorkingSet::loadToArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex, KConfigGroup group) +void WorkingSet::loadToArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex, KConfigGroup setGroup, KConfigGroup areaGroup) { - if (group.hasKey("Orientation")) { - QStringList subgroups = group.groupList(); + if (setGroup.hasKey("Orientation")) { + QStringList subgroups = setGroup.groupList(); if (subgroups.contains("0") && subgroups.contains("1")) { // kDebug() << "has zero, split:" << split; - Qt::Orientation orientation = group.readEntry("Orientation", "Horizontal") == "Vertical" ? Qt::Vertical : Qt::Horizontal; + Qt::Orientation orientation = setGroup.readEntry("Orientation", "Horizontal") == "Vertical" ? Qt::Vertical : Qt::Horizontal; if(!areaIndex->isSplitted()){ areaIndex->split(orientation); }else{ areaIndex->setOrientation(orientation); } - loadToArea(area, areaIndex->first(), KConfigGroup(&group, "0")); + loadToArea(area, areaIndex->first(), KConfigGroup(&setGroup, "0"), KConfigGroup(&areaGroup, "0")); - loadToArea(area, areaIndex->second(), KConfigGroup(&group, "1")); + loadToArea(area, areaIndex->second(), KConfigGroup(&setGroup, "1"), KConfigGroup(&areaGroup, "1")); } } else { while (areaIndex->isSplitted()) { - areaIndex = areaIndex->first(); - Q_ASSERT(areaIndex);// Split area index did not contain a first child area index if this fails - kDebug() << "is already splitted, using first index" << areaIndex; + Q_ASSERT(areaIndex->first()); + areaIndex->unsplit(areaIndex->second()); } - int viewCount = group.readEntry("View Count", 0); + //Track all documents in this areaIndex by their documentSpecifier + QHash viewsBySpec; + foreach (Sublime::View* view, areaIndex->views()) { + viewsBySpec.insert(view->document()->documentSpecifier(), view); + } + //Load all documents from the workingset into this areaIndex + int viewCount = setGroup.readEntry("View Count", 0); for (int i = 0; i < viewCount; ++i) { - QString type = group.readEntry(QString("View %1 Type").arg(i), ""); - QString specifier = group.readEntry(QString("View %1").arg(i), ""); + QString type = setGroup.readEntry(QString("View %1 Type").arg(i), ""); + QString specifier = setGroup.readEntry(QString("View %1").arg(i), ""); - bool viewExists = false; - foreach (Sublime::View* view, areaIndex->views()) { - if (view->document()->documentSpecifier() == specifier) { - viewExists = true; - break; - } - } - - if (viewExists) { + if (viewsBySpec.contains(specifier)) { kDebug() << "View already exists!"; continue; } IDocument* doc = Core::self()->documentControllerInternal()->openDocument(specifier, KTextEditor::Cursor::invalid(), IDocumentController::DoNotActivate | IDocumentController::DoNotCreateView); Sublime::Document *document = dynamic_cast(doc); if (document) { - kDebug() << document->title(); Sublime::View* view = document->createView(); - - QString state = group.readEntry(QString("View %1 State").arg(i), ""); - if (!state.isEmpty()) - view->setState(state); - area->addView(view, areaIndex); + viewsBySpec.insert(specifier, view); } else { kWarning() << "Unable to create view of type " << type; } } + //Now use the workingset's area config (if present) to reorder the documents and load their state + Sublime::View *lastView = 0; + viewCount = areaGroup.readEntry("View Count", 0); + for (int i = 0; i < viewCount; ++i) + { + QString specifier = areaGroup.readEntry(QString("View %1").arg(i)); + if (!viewsBySpec.contains(specifier)) + continue; + Sublime::View *view = viewsBySpec[specifier]; + + if (lastView) + area->addView(area->removeView(view), areaIndex, lastView); + + QString state = areaGroup.readEntry(QString("View %1 State").arg(i)); + if (state.length()) + view->setState(state); + + lastView = view; + } } } void deleteGroupRecursive(KConfigGroup group) { // kDebug() << "deleting" << group.name(); foreach(const QString& entry, group.entryMap().keys()) { group.deleteEntry(entry); } Q_ASSERT(group.entryMap().isEmpty()); foreach(const QString& subGroup, group.groupList()) { deleteGroupRecursive(group.group(subGroup)); group.deleteGroup(subGroup); } //Why doesn't this work? // Q_ASSERT(group.groupList().isEmpty()); group.deleteGroup(); #ifdef SYNC_OFTEN group.sync(); #endif } void WorkingSet::deleteSet(bool force, bool silent) { if(m_areas.isEmpty() || force) { emit aboutToRemove(this); KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); deleteGroupRecursive(group); #ifdef SYNC_OFTEN setConfig.sync(); #endif if(!silent) emit setChangedSignificantly(); } } void WorkingSet::saveFromArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex) { kDebug() << "saving" << m_id << "from area"; bool wasPersistent = isPersistent(); KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); - KConfigGroup group = setConfig.group(m_id); - deleteGroupRecursive(group); - group.writeEntry("iconName", m_iconName); - if (area->activeView()) { - group.writeEntry("Active View", area->activeView()->document()->documentSpecifier()); - } else { - group.writeEntry("Active View", QString()); - } - saveFromArea(area, areaIndex, group); + + KConfigGroup setGroup = setConfig.group(m_id); + deleteGroupRecursive(setGroup); + setGroup.writeEntry("iconName", m_iconName); + + KConfigGroup areaGroup = setConfig.group(m_id + '|' + area->title()); + QString lastActiveView = areaGroup.readEntry("Active View", ""); + deleteGroupRecursive(setGroup); + if (area->activeView() && area->activeView()->document()) + areaGroup.writeEntry("Active View", area->activeView()->document()->documentSpecifier()); + else + areaGroup.writeEntry("Active View", lastActiveView); + + saveFromArea(area, areaIndex, setGroup, areaGroup); if(isEmpty()) - deleteGroupRecursive(group); + { + deleteGroupRecursive(setGroup); + deleteGroupRecursive(areaGroup); + } setPersistent(wasPersistent); #ifdef SYNC_OFTEN setConfig.sync(); #endif emit setChangedSignificantly(); } void WorkingSet::areaViewAdded(Sublime::AreaIndex*, Sublime::View*) { Sublime::Area* area = qobject_cast(sender()); Q_ASSERT(area); Q_ASSERT(area->workingSet() == m_id); kDebug() << "added view in" << area << ", id" << m_id; if (m_loading) { kDebug() << "doing nothing because loading"; return; } changed(area); } void WorkingSet::areaViewRemoved(Sublime::AreaIndex*, Sublime::View* view) { Sublime::Area* area = qobject_cast(sender()); Q_ASSERT(area); Q_ASSERT(area->workingSet() == m_id); kDebug() << "removed view in" << area << ", id" << m_id; if (m_loading) { kDebug() << "doing nothing because loading"; return; } foreach(Sublime::Area* otherArea, m_areas) { if(otherArea == area) continue; bool hadDocument = false; foreach(Sublime::View* areaView, otherArea->views()) if(view->document() == areaView->document()) hadDocument = true; if(!hadDocument) { // We do this to prevent UI flicker. The view has already been removed from // one of the connected areas, so the working-set has already recorded the change. return; } } changed(area); } void WorkingSet::setPersistent(bool persistent) { KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); group.writeEntry("persistent", persistent); #ifdef SYNC_OFTEN group.sync(); #endif kDebug() << "setting" << m_id << "persistent:" << persistent; } bool WorkingSet::isPersistent() const { KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); return group.readEntry("persistent", false); } QIcon WorkingSet::inactiveIcon() const { if(isPersistent()) return m_inactiveIcon; else return m_inactiveNonPersistentIcon; } bool WorkingSet::isConnected( Sublime::Area* area ) { return m_areas.contains( area ); } QString WorkingSet::id() const { return m_id; } WorkingSet* WorkingSet::clone() { WorkingSet* ret = new WorkingSet( *this ); return ret; } bool WorkingSet::hasConnectedAreas() const { return !m_areas.isEmpty(); } bool WorkingSet::hasConnectedAreas( QList< Sublime::Area* > areas ) const { foreach( Sublime::Area* area, areas ) if ( m_areas.contains( area ) ) return true; return false; } void WorkingSet::connectArea( Sublime::Area* area ) { if ( m_areas.contains( area ) ) { kDebug() << "tried to double-connect area"; return; } kDebug() << "connecting" << m_id << "to area" << area; // Q_ASSERT(area->workingSet() == m_id); m_areas.push_back( area ); connect( area, SIGNAL( viewAdded( Sublime::AreaIndex*, Sublime::View* ) ), this, SLOT( areaViewAdded( Sublime::AreaIndex*, Sublime::View* ) ) ); connect( area, SIGNAL( viewRemoved( Sublime::AreaIndex*, Sublime::View* ) ), this, SLOT( areaViewRemoved( Sublime::AreaIndex*, Sublime::View* ) ) ); } void WorkingSet::disconnectArea( Sublime::Area* area ) { if ( !m_areas.contains( area ) ) { kDebug() << "tried to disconnect not connected area"; return; } kDebug() << "disconnecting" << m_id << "from area" << area; // Q_ASSERT(area->workingSet() == m_id); disconnect( area, SIGNAL( viewAdded( Sublime::AreaIndex*, Sublime::View* ) ), this, SLOT( areaViewAdded( Sublime::AreaIndex*, Sublime::View* ) ) ); disconnect( area, SIGNAL( viewRemoved( Sublime::AreaIndex*, Sublime::View* ) ), this, SLOT( areaViewRemoved( Sublime::AreaIndex*, Sublime::View* ) ) ); m_areas.removeAll( area ); } void WorkingSet::deleteSet() { deleteSet( false ); } void WorkingSet::changed( Sublime::Area* area ) { if ( m_loading ) { return; } { //Do not capture changes done while loading PushValue enableLoading( m_loading, true ); kDebug() << "recording change done to" << m_id; saveFromArea( area, area->rootIndex() ); for ( QList< QPointer< Sublime::Area > >::iterator it = m_areas.begin(); it != m_areas.end(); ++it ) { if (( *it ) != area ) { loadToArea(( *it ), ( *it )->rootIndex() ); } } } emit setChangedSignificantly(); } QIcon WorkingSet::activeIcon() const { return m_activeIcon; } QString WorkingSet::iconName() const { return m_iconName; } #include "workingset.moc" diff --git a/shell/workingsets/workingset.h b/shell/workingsets/workingset.h index 512c5cb92f..22a0a13d84 100644 --- a/shell/workingsets/workingset.h +++ b/shell/workingsets/workingset.h @@ -1,105 +1,105 @@ /* 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. */ #ifndef WORKINGSET_H #define WORKINGSET_H #include #include #include #include namespace Sublime { class Area; class AreaIndex; class View; } namespace KDevelop { class WorkingSet : public QObject { Q_OBJECT public: WorkingSet(QString id, QString icon); bool isConnected(Sublime::Area* area); QString iconName() const; QIcon activeIcon() const; QIcon inactiveIcon() const; bool isPersistent() const; void setPersistent(bool persistent); QString id() const; ///Creates a copy of this working-set with a new identity WorkingSet* clone(); QStringList fileList() const; bool isEmpty() const; ///Updates this working-set from the given area and area-index void saveFromArea(Sublime::Area* area, Sublime::AreaIndex * areaIndex); ///Loads this working-set directly from the configuration file, and stores it in the given area ///@param clear If this is true, the area will be cleared before void loadToArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex, bool clear = true); bool hasConnectedAreas() const; bool hasConnectedAreas(QList areas) const; void connectArea(Sublime::Area* area); void disconnectArea(Sublime::Area* area); void deleteSet(bool force, bool silent = false); private slots: void deleteSet(); void areaViewAdded(Sublime::AreaIndex* /*index*/, Sublime::View* /*view*/); void areaViewRemoved(Sublime::AreaIndex* /*index*/, Sublime::View* /*view*/); signals: void setChangedSignificantly(); void aboutToRemove(WorkingSet*); private: void changed(Sublime::Area* area); - void saveFromArea(Sublime::Area* area, Sublime::AreaIndex * areaIndex, KConfigGroup & group); - void loadToArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex, KConfigGroup group); + void saveFromArea(Sublime::Area* area, Sublime::AreaIndex *areaIndex, KConfigGroup setGroup, KConfigGroup areaGroup); + void loadToArea(Sublime::Area* area, Sublime::AreaIndex *areaIndex, KConfigGroup setGroup, KConfigGroup areaGroup); WorkingSet(const WorkingSet& rhs); QString m_id; QString m_iconName; QIcon m_activeIcon, m_inactiveIcon, m_inactiveNonPersistentIcon; QList > m_areas; static bool m_loading; }; } #endif // WORKINGSET_H diff --git a/sublime/areaindex.h b/sublime/areaindex.h index 7dfc13f7f3..c982d8206e 100644 --- a/sublime/areaindex.h +++ b/sublime/areaindex.h @@ -1,191 +1,191 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef SUBLIMEAREAINDEX_H #define SUBLIMEAREAINDEX_H #include #include #include #include "sublimeexport.h" namespace Sublime { class View; /** @short Index denotes the position of the view in the splitted area. B-Tree alike structure is used to represent an area with splitted views. Area has a root index which can either contain one view or contain two child nodes (@p first and @p second). In the later case area is considered to be splitted into two parts. Each of those parts can in turn contain a view or be splitted (with first/second children). When a view at given index is splitted, then its index becomes an index of the splitter and the original view goes into the @p first part of the splitter. The new view goes as @p second part. For example, consider an area which was splitted once horizontally and then the second part of it was splitted vertically: @code 1. initial state: one view in the area |----------------| | | | 1 | | | |----------------| Indices: root_index (view 1) 2. the view is splitted horizontally |----------------| | | | | 1 | 2 | | | | |----------------| Indices: root_index (no view) | ---------------- | | view 1 view 2 3. the second view is splitted vertically |----------------| | | 2 | | 1 |--------| | | 3 | |----------------| Indices: root_index (horizontal splitter) | ---------------- | | view 1 vertical_splitter | ----------------- | | view 2 view 3 @endcode It is possible that several "stacked" views will have the same area index. Those views can be considered as the view stack from which only one view is visible at the time. @code |----------------| | | | 1,2,3,4 | | | |----------------| Indices: root_index (view1, view2, view3, view4) @endcode */ class SUBLIME_EXPORT AreaIndex { public: ~AreaIndex(); AreaIndex(const AreaIndex &index); /**@return the parent index, returns 0 for root index.*/ AreaIndex *parent() const; /**@return the first child index if there're any.*/ AreaIndex *first() const; /**@return the second child index if there're any.*/ AreaIndex *second() const; /**@return true if the index is splitted.*/ bool isSplitted() const; /**@return the orientation of the splitter for this index.*/ Qt::Orientation orientation() const; /**Set the orientation of the splitter for this index.*/ void setOrientation(Qt::Orientation orientation) const; /**Adds view to the list of views in this position. Does nothing if the view is already splitted. @param after if not 0, new view will be placed after this one. @param view the view to be added.*/ void add(View *view, View *after = 0); /**Removes view and unsplits the parent index when no views are left at the current index.*/ void remove(View *view); /**Splits the view in this position by given @p orientation and adds the @p newView into the splitter. Does nothing if the view is already splitted. @p newView will be in the second child index.*/ void split(View *newView, Qt::Orientation orientation); /**Splits the view in this position by given @p orientation. Does nothing if the view is already splitted.*/ void split(Qt::Orientation orientation); + /**Unsplits the index removing the given @p childToRemove and moving the contents + of another child to this index.*/ + void unsplit(AreaIndex *childToRemove); /**@return the stacked view in @p position, returns 0 for splitter's indices and when there's no view at the @p position.*/ View *viewAt(int position) const; /**@return the number of stacked views.*/ int viewCount() const; /**@return true if there's a stacked @p view at this index.*/ bool hasView(View *view) const; /**@return the list of views at this index.*/ QList &views() const; protected: /**Constructor for Root index.*/ AreaIndex(); private: /**Constructor for indices other than root.*/ AreaIndex(AreaIndex *parent); /**Sets the parent for this index.*/ void setParent(AreaIndex *parent); /**Copies the data from this index to @p target.*/ void copyTo(AreaIndex *target); /**Copies the children indices from this index to @p target.*/ void copyChildrenTo(AreaIndex *target); - /**Unsplits the index removing the given @p child and moving the contents - of another child to this index.*/ - void unsplit(AreaIndex *childToRemove); struct AreaIndexPrivate * const d; }; /** @short Root Area Index This is the special index class returned by @ref Area::rootIndex(). Doesn't provide any additional functionality beyond AreaIndex. */ class SUBLIME_EXPORT RootAreaIndex: public AreaIndex { public: RootAreaIndex(); private: class RootAreaIndexPrivate* const d; }; } #endif