diff --git a/language/duchain/ducontext.cpp b/language/duchain/ducontext.cpp index 9451b322a5..66c1bf088a 100644 --- a/language/duchain/ducontext.cpp +++ b/language/duchain/ducontext.cpp @@ -1,1748 +1,1747 @@ /* This 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. */ #include "ducontext.h" #include #include #include #include #include "ducontextdata.h" #include "declaration.h" #include "duchain.h" #include "duchainlock.h" #include "use.h" #include "identifier.h" #include "topducontext.h" #include "persistentsymboltable.h" #include "aliasdeclaration.h" #include "namespacealiasdeclaration.h" #include "abstractfunctiondeclaration.h" #include "indexedstring.h" #include "duchainregister.h" #include "topducontextdynamicdata.h" #include "importers.h" #include "uses.h" #include "navigation/abstractdeclarationnavigationcontext.h" #include "navigation/abstractnavigationwidget.h" #include "ducontextdynamicdata.h" -#include "abstractfunctiondeclaration.h" ///It is fine to use one global static mutex here const uint maxParentDepth = 20; using namespace KTextEditor; //Stored statically for performance-reasons #ifndef NDEBUG #define ENSURE_CAN_WRITE_(x) {if(x->inDUChain()) { ENSURE_CHAIN_WRITE_LOCKED }} #define ENSURE_CAN_READ_(x) {if(x->inDUChain()) { ENSURE_CHAIN_READ_LOCKED }} #else #define ENSURE_CAN_WRITE_(x) #define ENSURE_CAN_READ_(x) #endif namespace KDevelop { QMutex DUContextDynamicData::m_localDeclarationsMutex(QMutex::Recursive); DEFINE_LIST_MEMBER_HASH(DUContextData, m_childContexts, LocalIndexedDUContext) DEFINE_LIST_MEMBER_HASH(DUContextData, m_importers, IndexedDUContext) DEFINE_LIST_MEMBER_HASH(DUContextData, m_importedContexts, DUContext::Import) DEFINE_LIST_MEMBER_HASH(DUContextData, m_localDeclarations, LocalIndexedDeclaration) DEFINE_LIST_MEMBER_HASH(DUContextData, m_uses, Use) REGISTER_DUCHAIN_ITEM(DUContext); DUChainVisitor::~DUChainVisitor() { } /** * We leak here, to prevent a possible crash during destruction, as the destructor * of Identifier is not safe to be called after the duchain has been destroyed */ Identifier& globalImportIdentifier() { static Identifier globalImportIdentifierObject(*new Identifier("{...import...}")); return globalImportIdentifierObject; } Identifier& globalAliasIdentifier() { static Identifier globalAliasIdentifierObject(*new Identifier("{...alias...}")); return globalAliasIdentifierObject; } void DUContext::rebuildDynamicData(DUContext* parent, uint ownIndex) { Q_ASSERT(!parent || ownIndex); m_dynamicData->m_topContext = parent ? parent->topContext() : static_cast(this); m_dynamicData->m_indexInTopContext = ownIndex; m_dynamicData->m_parentContext = DUContextPointer(parent); m_dynamicData->m_context = this; DUChainBase::rebuildDynamicData(parent, ownIndex); } DUContextData::DUContextData() : m_inSymbolTable(false) , m_anonymousInParent(false) , m_propagateDeclarations(false) { initializeAppendedLists(); } DUContextData::~DUContextData() { freeAppendedLists(); } DUContextData::DUContextData(const DUContextData& rhs) : DUChainBaseData(rhs) , m_inSymbolTable(rhs.m_inSymbolTable) , m_anonymousInParent(rhs.m_anonymousInParent) , m_propagateDeclarations(rhs.m_propagateDeclarations) { initializeAppendedLists(); copyListsFrom(rhs); m_scopeIdentifier = rhs.m_scopeIdentifier; m_contextType = rhs.m_contextType; m_owner = rhs.m_owner; } DUContextDynamicData::DUContextDynamicData(DUContext* d) : m_topContext(0) , m_hasLocalDeclarationsHash(false) , m_indexInTopContext(0) , m_context(d) , m_rangesChanged(true) { } void DUContextDynamicData::scopeIdentifier(bool includeClasses, QualifiedIdentifier& target) const { if (m_parentContext) m_parentContext->m_dynamicData->scopeIdentifier(includeClasses, target); if (includeClasses || m_context->d_func()->m_contextType != DUContext::Class) target += m_context->d_func()->m_scopeIdentifier; } bool DUContextDynamicData::importsSafeButSlow(const DUContext* context, const TopDUContext* source, ImportsHash& checked) const { if( this == context->m_dynamicData ) return true; if(checked.find(this) != checked.end()) return false; checked.insert(std::make_pair(this, true)); FOREACH_FUNCTION( const DUContext::Import& ctx, m_context->d_func()->m_importedContexts ) { DUContext* import = ctx.context(source); if(import == context || (import && import->m_dynamicData->importsSafeButSlow(context, source, checked))) return true; } return false; } bool DUContextDynamicData::imports(const DUContext* context, const TopDUContext* source, int maxDepth) const { if( this == context->m_dynamicData ) return true; if(maxDepth == 0) { ImportsHash checked(500); checked.set_empty_key(0); return importsSafeButSlow(context, source, checked); } FOREACH_FUNCTION( const DUContext::Import& ctx, m_context->d_func()->m_importedContexts ) { DUContext* import = ctx.context(source); if(import == context || (import && import->m_dynamicData->imports(context, source, maxDepth-1))) return true; } return false; } void DUContextDynamicData::enableLocalDeclarationsHash(DUContext* ctx, const Identifier& currentIdentifier, Declaration* currentDecl) { m_hasLocalDeclarationsHash = true; FOREACH_FUNCTION(const LocalIndexedDeclaration& indexedDecl, ctx->d_func()->m_localDeclarations) { Declaration* decl = indexedDecl.data(m_topContext); Q_ASSERT(decl); if(currentDecl != decl) m_localDeclarationsHash.insert( decl->identifier(), DeclarationPointer(decl) ); else m_localDeclarationsHash.insert( currentIdentifier, DeclarationPointer(decl) ); } FOREACH_FUNCTION(const LocalIndexedDUContext& child, ctx->d_func()->m_childContexts) { DUContext* childCtx = child.data(m_topContext); Q_ASSERT(childCtx); if(childCtx->d_func()->m_propagateDeclarations) enableLocalDeclarationsHash(childCtx, currentIdentifier, currentDecl); } } void DUContextDynamicData::disableLocalDeclarationsHash() { m_hasLocalDeclarationsHash = false; m_localDeclarationsHash.clear(); } bool DUContextDynamicData::needsLocalDeclarationsHash() { ///@todo Do this again, it brings a large performance boost //For now disable the hash, until we make sure that all declarations needed for the hash are loaded first //including those in propagating sub-contexts. //Then, also make sure that we create the declaration hash after loading if needed return false; if(m_context->d_func()->m_localDeclarationsSize() > 15) return true; uint propagatingChildContexts = 0; FOREACH_FUNCTION(const LocalIndexedDUContext& child, m_context->d_func()->m_childContexts) { DUContext* childCtx = child.data(m_topContext); Q_ASSERT(childCtx); if(childCtx->d_func()->m_propagateDeclarations) ++propagatingChildContexts; } return propagatingChildContexts > 4; } void DUContextDynamicData::addDeclarationToHash(const Identifier& identifier, Declaration* declaration) { if(m_hasLocalDeclarationsHash) m_localDeclarationsHash.insert( identifier, DeclarationPointer(declaration) ); if( m_context->d_func()->m_propagateDeclarations && m_parentContext ) m_parentContext->m_dynamicData->addDeclarationToHash(identifier, declaration); if(!m_hasLocalDeclarationsHash && needsLocalDeclarationsHash()) enableLocalDeclarationsHash(m_context, identifier, declaration); } void DUContextDynamicData::removeDeclarationFromHash(const Identifier& identifier, Declaration* declaration) { if(m_hasLocalDeclarationsHash) m_localDeclarationsHash.remove( identifier, DeclarationPointer(declaration) ); if( m_context->d_func()->m_propagateDeclarations && m_parentContext ) m_parentContext->m_dynamicData->removeDeclarationFromHash(identifier, declaration); if(m_hasLocalDeclarationsHash && !needsLocalDeclarationsHash()) disableLocalDeclarationsHash(); } inline bool isContextTemporary(uint index) { return index > (0xffffffff/2); } void DUContextDynamicData::addDeclaration( Declaration * newDeclaration ) { // The definition may not have its identifier set when it's assigned... // allow dupes here, TODO catch the error elsewhere { QMutexLocker lock(&m_localDeclarationsMutex); // m_localDeclarations.append(newDeclaration); //If this context is temporary, added declarations should be as well, and viceversa Q_ASSERT(isContextTemporary(m_indexInTopContext) == isContextTemporary(newDeclaration->ownIndex())); CursorInRevision start = newDeclaration->range().start; ///@todo Do binary search to find the position bool inserted = false; for (int i = m_context->d_func_dynamic()->m_localDeclarationsSize()-1; i >= 0; --i) { Declaration* child = m_context->d_func_dynamic()->m_localDeclarations()[i].data(m_topContext); if(!child) { kWarning() << "child declaration number" << i << "of" << m_context->d_func_dynamic()->m_localDeclarationsSize() << "is invalid"; continue; } if(child == newDeclaration) return; //TODO: All declarations in a macro will have the same empty range, and just get appended //that may not be Good Enough in complex cases. if (start >= child->range().start) { m_context->d_func_dynamic()->m_localDeclarationsList().insert(i+1, newDeclaration); if(!m_context->d_func()->m_localDeclarations()[i+1].data(m_topContext)) kFatal() << "Inserted a not addressable declaration"; inserted = true; break; } } if( !inserted ) //We haven't found any child that is before this one, so prepend it m_context->d_func_dynamic()->m_localDeclarationsList().insert(0, newDeclaration); addDeclarationToHash(newDeclaration->identifier(), newDeclaration); } //DUChain::contextChanged(m_context, DUChainObserver::Addition, DUChainObserver::LocalDeclarations, newDeclaration); } bool DUContextDynamicData::removeDeclaration(Declaration* declaration) { QMutexLocker lock(&m_localDeclarationsMutex); if(!m_topContext->deleting()) //We can save a lot of time by just not caring about the hash while deleting removeDeclarationFromHash(declaration->identifier(), declaration); if( m_context->d_func_dynamic()->m_localDeclarationsList().removeOne(LocalIndexedDeclaration(declaration)) ) { //DUChain::contextChanged(m_context, DUChainObserver::Removal, DUChainObserver::LocalDeclarations, declaration); return true; }else { return false; } } void DUContext::changingIdentifier( Declaration* decl, const Identifier& from, const Identifier& to ) { QMutexLocker lock(&DUContextDynamicData::m_localDeclarationsMutex); m_dynamicData->removeDeclarationFromHash(from, decl); m_dynamicData->addDeclarationToHash(to, decl); } void DUContextDynamicData::addChildContext( DUContext * context ) { // Internal, don't need to assert a lock Q_ASSERT(!context->m_dynamicData->m_parentContext || context->m_dynamicData->m_parentContext.data()->m_dynamicData == this ); LocalIndexedDUContext indexed(context->m_dynamicData->m_indexInTopContext); //If this context is temporary, added declarations should be as well, and viceversa Q_ASSERT(isContextTemporary(m_indexInTopContext) == isContextTemporary(indexed.localIndex())); bool inserted = false; int childCount = m_context->d_func()->m_childContextsSize(); for (int i = childCount-1; i >= 0; --i) {///@todo Do binary search to find the position DUContext* child = m_context->d_func()->m_childContexts()[i].data(m_topContext); if (context == child) return; if (context->range().start >= child->range().start) { m_context->d_func_dynamic()->m_childContextsList().insert(i+1, indexed); context->m_dynamicData->m_parentContext = m_context; inserted = true; break; } } if( !inserted ) { m_context->d_func_dynamic()->m_childContextsList().insert(0, indexed); context->m_dynamicData->m_parentContext = m_context; } if(context->d_func()->m_propagateDeclarations) { QMutexLocker lock(&DUContextDynamicData::m_localDeclarationsMutex); disableLocalDeclarationsHash(); if(needsLocalDeclarationsHash()) enableLocalDeclarationsHash(m_context); } //DUChain::contextChanged(m_context, DUChainObserver::Addition, DUChainObserver::ChildContexts, context); } bool DUContextDynamicData::removeChildContext( DUContext* context ) { // ENSURE_CAN_WRITE if( m_context->d_func_dynamic()->m_childContextsList().removeOne(LocalIndexedDUContext(context)) ) return true; else return false; } void DUContextDynamicData::addImportedChildContext( DUContext * context ) { // ENSURE_CAN_WRITE DUContext::Import import(m_context, context); if(import.isDirect()) { //Direct importers are registered directly within the data if(m_context->d_func_dynamic()->m_importersList().contains(IndexedDUContext(context))) { kDebug(9505) << m_context->scopeIdentifier(true).toString() << "importer added multiple times:" << context->scopeIdentifier(true).toString(); return; } m_context->d_func_dynamic()->m_importersList().append(context); }else{ //Indirect importers are registered separately Importers::self().addImporter(import.indirectDeclarationId(), IndexedDUContext(context)); } //DUChain::contextChanged(m_context, DUChainObserver::Addition, DUChainObserver::ImportedChildContexts, context); } //Can also be called with a context that is not in the list void DUContextDynamicData::removeImportedChildContext( DUContext * context ) { // ENSURE_CAN_WRITE DUContext::Import import(m_context, context); if(import.isDirect()) { m_context->d_func_dynamic()->m_importersList().removeOne(IndexedDUContext(context)); }else{ //Indirect importers are registered separately Importers::self().removeImporter(import.indirectDeclarationId(), IndexedDUContext(context)); } } int DUContext::depth() const { { if (!parentContext()) return 0; return parentContext()->depth() + 1; } } DUContext::DUContext(DUContextData& data) : DUChainBase(data) , m_dynamicData(new DUContextDynamicData(this)) { } DUContext::DUContext(const RangeInRevision& range, DUContext* parent, bool anonymous) : DUChainBase(*new DUContextData(), range) , m_dynamicData(new DUContextDynamicData(this)) { d_func_dynamic()->setClassId(this); if(parent) m_dynamicData->m_topContext = parent->topContext(); else m_dynamicData->m_topContext = static_cast(this); d_func_dynamic()->setClassId(this); DUCHAIN_D_DYNAMIC(DUContext); d->m_contextType = Other; m_dynamicData->m_parentContext = 0; d->m_anonymousInParent = anonymous; d->m_inSymbolTable = false; if (parent) { m_dynamicData->m_indexInTopContext = parent->topContext()->m_dynamicData->allocateContextIndex(this, parent->isAnonymous() || anonymous); Q_ASSERT(m_dynamicData->m_indexInTopContext); if( !anonymous ) parent->m_dynamicData->addChildContext(this); else m_dynamicData->m_parentContext = parent; } if(parent && !anonymous && parent->inSymbolTable()) setInSymbolTable(true); } bool DUContext::isAnonymous() const { return d_func()->m_anonymousInParent || (m_dynamicData->m_parentContext && m_dynamicData->m_parentContext->isAnonymous()); } DUContext::DUContext( DUContextData& dd, const RangeInRevision& range, DUContext * parent, bool anonymous ) : DUChainBase(dd, range) , m_dynamicData(new DUContextDynamicData(this)) { if(parent) m_dynamicData->m_topContext = parent->topContext(); else m_dynamicData->m_topContext = static_cast(this); DUCHAIN_D_DYNAMIC(DUContext); d->m_contextType = Other; m_dynamicData->m_parentContext = 0; d->m_inSymbolTable = false; d->m_anonymousInParent = anonymous; if (parent) { m_dynamicData->m_indexInTopContext = parent->topContext()->m_dynamicData->allocateContextIndex(this, parent->isAnonymous() || anonymous); if( !anonymous ) parent->m_dynamicData->addChildContext(this); else m_dynamicData->m_parentContext = parent; } } DUContext::DUContext(DUContext& useDataFrom) : DUChainBase(useDataFrom) , m_dynamicData(useDataFrom.m_dynamicData) { } DUContext::~DUContext( ) { TopDUContext* top = topContext(); if(!top->deleting() || !top->isOnDisk()) { DUCHAIN_D_DYNAMIC(DUContext); if(d->m_owner.declaration()) d->m_owner.declaration()->setInternalContext(0); while( d->m_importersSize() != 0 ) { if(d->m_importers()[0].data()) d->m_importers()[0].data()->removeImportedParentContext(this); else { kDebug() << "importer disappeared"; d->m_importersList().removeOne(d->m_importers()[0]); } } clearImportedParentContexts(); } deleteChildContextsRecursively(); if(!topContext()->deleting() || !topContext()->isOnDisk()) deleteUses(); deleteLocalDeclarations(); //If the top-context is being delete, we don't need to spend time rebuilding the inner structure. //That's expensive, especially when the data is not dynamic. if(!top->deleting() || !top->isOnDisk()) { if (m_dynamicData->m_parentContext) m_dynamicData->m_parentContext->m_dynamicData->removeChildContext(this); //DUChain::contextChanged(this, DUChainObserver::Deletion, DUChainObserver::NotApplicable); } top->m_dynamicData->clearContextIndex(this); Q_ASSERT(d_func()->isDynamic() == (!top->deleting() || !top->isOnDisk() || top->m_dynamicData->isTemporaryContextIndex(m_dynamicData->m_indexInTopContext))); delete m_dynamicData; } QVector< DUContext * > DUContext::childContexts( ) const { ENSURE_CAN_READ QVector< DUContext * > ret; FOREACH_FUNCTION(const LocalIndexedDUContext& ctx, d_func()->m_childContexts) ret << ctx.data(topContext()); return ret; } Declaration* DUContext::owner() const { ENSURE_CAN_READ return d_func()->m_owner.declaration(); } void DUContext::setOwner(Declaration* owner) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); if( owner == d->m_owner.declaration() ) return; Declaration* oldOwner = d->m_owner.declaration(); d->m_owner = owner; //Q_ASSERT(!oldOwner || oldOwner->internalContext() == this); if( oldOwner && oldOwner->internalContext() == this ) oldOwner->setInternalContext(0); //The context set as internal context should always be the last opened context if( owner ) owner->setInternalContext(this); } DUContext* DUContext::parentContext( ) const { //ENSURE_CAN_READ Commented out for performance reasons return m_dynamicData->m_parentContext.data(); } void DUContext::setPropagateDeclarations(bool propagate) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); QMutexLocker lock(&DUContextDynamicData::m_localDeclarationsMutex); if(propagate == d->m_propagateDeclarations) return; m_dynamicData->m_parentContext->m_dynamicData->disableLocalDeclarationsHash(); d->m_propagateDeclarations = propagate; if(m_dynamicData->m_parentContext->m_dynamicData->needsLocalDeclarationsHash()) m_dynamicData->m_parentContext->m_dynamicData->enableLocalDeclarationsHash(m_dynamicData->m_parentContext.data()); } bool DUContext::isPropagateDeclarations() const { return d_func()->m_propagateDeclarations; } QList DUContext::findLocalDeclarations( const Identifier& identifier, const CursorInRevision & position, const TopDUContext* topContext, const AbstractType::Ptr& dataType, SearchFlags flags ) const { ENSURE_CAN_READ DeclarationList ret; findLocalDeclarationsInternal(identifier, position.isValid() ? position : range().end, dataType, ret, topContext ? topContext : this->topContext(), flags); return ret.toList(); } bool contextIsChildOrEqual(const DUContext* childContext, const DUContext* context) { if(childContext == context) return true; if(childContext->parentContext()) return contextIsChildOrEqual(childContext->parentContext(), context); else return false; } void DUContext::findLocalDeclarationsInternal( const Identifier& identifier, const CursorInRevision & position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* /*source*/, SearchFlags flags ) const { { QMutexLocker lock(&DUContextDynamicData::m_localDeclarationsMutex); struct Checker { Checker(SearchFlags flags, const AbstractType::Ptr& dataType, const CursorInRevision & position, DUContext::ContextType ownType) : m_flags(flags), m_dataType(dataType), m_position(position), m_ownType(ownType) { } Declaration* check(Declaration* declaration) { ///@todo This is C++-specific if (m_ownType != Class && m_ownType != Template && m_position.isValid() && m_position <= declaration->range().start) { return 0; } if( declaration->kind() == Declaration::Alias && !(m_flags & DontResolveAliases) ) { //Apply alias declarations AliasDeclaration* alias = static_cast(declaration); if(alias->aliasedDeclaration().isValid()) { declaration = alias->aliasedDeclaration().declaration(); } else { #ifndef Q_CC_MSVC kDebug() << "lost aliased declaration"; #endif } } if( declaration->kind() == Declaration::NamespaceAlias && !(m_flags & NoFiltering) ) return 0; if((m_flags & OnlyFunctions) && !declaration->isFunctionDeclaration()) return 0; if (m_dataType && m_dataType->indexed() != declaration->indexedType()) { return 0; } return declaration; } SearchFlags m_flags; const AbstractType::Ptr m_dataType; const CursorInRevision& m_position; DUContext::ContextType m_ownType; }; Checker checker(flags, dataType, position, type()); if(m_dynamicData->m_hasLocalDeclarationsHash) { //Use a special hash that contains all declarations visible in this context QHash::const_iterator it = m_dynamicData->m_localDeclarationsHash.constFind(identifier); QHash::const_iterator end = m_dynamicData->m_localDeclarationsHash.constEnd(); for( ; it != end && it.key() == identifier; ++it ) { Declaration* declaration = (*it).data(); if( !declaration ) { //This should never happen, but let's see kDebug(9505) << "DUContext::findLocalDeclarationsInternal: Invalid declaration in local-declaration-hash"; continue; } Declaration* checked = checker.check(declaration); if(checked) ret.append(checked); } }else if(d_func()->m_inSymbolTable && !this->localScopeIdentifier().isEmpty() && !identifier.isEmpty()) { //This context is in the symbol table, use the symbol-table to speed up the search QualifiedIdentifier id(scopeIdentifier(true) + identifier); TopDUContext* top = topContext(); uint count; const IndexedDeclaration* declarations; PersistentSymbolTable::self().declarations(id, count, declarations); for(uint a = 0; a < count; ++a) { ///@todo Eventually do efficient iteration-free filtering if(declarations[a].topContextIndex() == top->ownIndex()) { Declaration* decl = LocalIndexedDeclaration(declarations[a].localIndex()).data(top); if(decl && contextIsChildOrEqual(decl->context(), this)) { Declaration* checked = checker.check(decl); if(checked) ret.append(checked); } } } }else { //Iterate through all declarations DUContextDynamicData::VisibleDeclarationIterator it(m_dynamicData); IndexedIdentifier indexedIdentifier(identifier); while(it) { Declaration* declaration = *it; if(declaration && declaration->indexedIdentifier() == indexedIdentifier) { Declaration* checked = checker.check(declaration); if(checked) ret.append(checked); } ++it; } } } } bool DUContext::foundEnough( const DeclarationList& ret, SearchFlags flags ) const { if( !ret.isEmpty() && !(flags & DUContext::NoFiltering)) return true; else return false; } bool DUContext::findDeclarationsInternal( const SearchItem::PtrList & baseIdentifiers, const CursorInRevision & position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* source, SearchFlags flags, uint depth ) const { if(depth > maxParentDepth) { kDebug() << "maximum depth reached in" << scopeIdentifier(true); return false; } DUCHAIN_D(DUContext); if( d_func()->m_contextType != Namespace ) { //If we're in a namespace, delay all the searching into the top-context, because only that has the overview to pick the correct declarations. for(int a = 0; a < baseIdentifiers.size(); ++a) if(!baseIdentifiers[a]->isExplicitlyGlobal && baseIdentifiers[a]->next.isEmpty()) //It makes no sense searching locally for qualified identifiers findLocalDeclarationsInternal(baseIdentifiers[a]->identifier, position, dataType, ret, source, flags); if( foundEnough(ret, flags) ) return true; } ///Step 1: Apply namespace-aliases and -imports SearchItem::PtrList aliasedIdentifiers; //Because of namespace-imports and aliases, this identifier may need to be searched under multiple names applyAliases(baseIdentifiers, aliasedIdentifiers, position, false, type() != DUContext::Namespace && type() != DUContext::Global); if( d->m_importedContextsSize() != 0 ) { ///Step 2: Give identifiers that are not marked as explicitly-global to imported contexts(explicitly global ones are treatead in TopDUContext) SearchItem::PtrList nonGlobalIdentifiers; FOREACH_ARRAY( const SearchItem::Ptr& identifier, aliasedIdentifiers ) if( !identifier->isExplicitlyGlobal ) nonGlobalIdentifiers << identifier; if( !nonGlobalIdentifiers.isEmpty() ) { for(int import = d->m_importedContextsSize()-1; import >= 0; --import ) { DUContext* context = d->m_importedContexts()[import].context(source); while( !context && import > 0 ) { --import; context = d->m_importedContexts()[import].context(source); } if(context == this) { kDebug() << "resolved self as import:" << scopeIdentifier(true); continue; } if( !context ) break; if( position.isValid() && d->m_importedContexts()[import].position.isValid() && position < d->m_importedContexts()[import].position ) continue; if( !context->findDeclarationsInternal(nonGlobalIdentifiers, url() == context->url() ? position : context->range().end, dataType, ret, source, flags | InImportedParentContext, depth+1) ) return false; } } } if( foundEnough(ret, flags) ) return true; ///Step 3: Continue search in parent-context if (!(flags & DontSearchInParent) && shouldSearchInParent(flags) && m_dynamicData->m_parentContext) { applyUpwardsAliases(aliasedIdentifiers, source); return m_dynamicData->m_parentContext->findDeclarationsInternal(aliasedIdentifiers, url() == m_dynamicData->m_parentContext->url() ? position : m_dynamicData->m_parentContext->range().end, dataType, ret, source, flags, depth); } return true; } QList< QualifiedIdentifier > DUContext::fullyApplyAliases(const QualifiedIdentifier& id, const TopDUContext* source) const { ENSURE_CAN_READ if(!source) source = topContext(); SearchItem::PtrList identifiers; identifiers << SearchItem::Ptr(new SearchItem(id)); const DUContext* current = this; while(current) { SearchItem::PtrList aliasedIdentifiers; current->applyAliases(identifiers, aliasedIdentifiers, CursorInRevision::invalid(), true, false); current->applyUpwardsAliases(identifiers, source); current = current->parentContext(); } QList ret; FOREACH_ARRAY(const SearchItem::Ptr& item, identifiers) ret += item->toList(); return ret; } QList DUContext::findDeclarations( const QualifiedIdentifier & identifier, const CursorInRevision & position, const AbstractType::Ptr& dataType, const TopDUContext* topContext, SearchFlags flags) const { ENSURE_CAN_READ DeclarationList ret; SearchItem::PtrList identifiers; identifiers << SearchItem::Ptr(new SearchItem(identifier)); findDeclarationsInternal(identifiers, position.isValid() ? position : range().end, dataType, ret, topContext ? topContext : this->topContext(), flags, 0); return ret.toList(); } bool DUContext::imports(const DUContext* origin, const CursorInRevision& /*position*/ ) const { ENSURE_CAN_READ return m_dynamicData->imports(origin, topContext(), 4); } bool DUContext::addIndirectImport(const DUContext::Import& import) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); for(unsigned int a = 0; a < d->m_importedContextsSize(); ++a) { if(d->m_importedContexts()[a] == import) { d->m_importedContextsList()[a].position = import.position; return true; } } ///Do not sort the imported contexts by their own line-number, it makes no sense. ///Contexts added first, aka template-contexts, should stay in first place, so they are searched first. d->m_importedContextsList().append(import); return false; } void DUContext::addImportedParentContext( DUContext * context, const CursorInRevision& position, bool anonymous, bool /*temporary*/ ) { ENSURE_CAN_WRITE if(context == this) { kDebug() << "Tried to import self"; return; } Import import(context, this, position); if(addIndirectImport(import)) return; if( !anonymous ) { ENSURE_CAN_WRITE_(context) context->m_dynamicData->addImportedChildContext(this); } //DUChain::contextChanged(this, DUChainObserver::Addition, DUChainObserver::ImportedParentContexts, context); } void DUContext::removeImportedParentContext( DUContext * context ) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); Import import(context, this, CursorInRevision::invalid()); for(unsigned int a = 0; a < d->m_importedContextsSize(); ++a) { if(d->m_importedContexts()[a] == import) { d->m_importedContextsList().remove(a); break; } } if( !context ) return; context->m_dynamicData->removeImportedChildContext(this); //DUChain::contextChanged(this, DUChainObserver::Removal, DUChainObserver::ImportedParentContexts, context); } KDevVarLengthArray DUContext::indexedImporters() const { KDevVarLengthArray ret; if(owner()) ret = Importers::self().importers(owner()->id()); //Add indirect importers to the list FOREACH_FUNCTION(const IndexedDUContext& ctx, d_func()->m_importers) ret.append(ctx); return ret; } QVector DUContext::importers() const { ENSURE_CAN_READ QVector ret; FOREACH_FUNCTION(const IndexedDUContext& ctx, d_func()->m_importers) ret << ctx.context(); if(owner()) { //Add indirect importers to the list KDevVarLengthArray indirect = Importers::self().importers(owner()->id()); FOREACH_ARRAY(const IndexedDUContext& ctx, indirect) { ret << ctx.context(); } } return ret; } DUContext * DUContext::findContext( const CursorInRevision& position, DUContext* parent) const { ENSURE_CAN_READ if (!parent) parent = const_cast(this); FOREACH_FUNCTION(const LocalIndexedDUContext& context, parent->d_func()->m_childContexts) if (context.data(topContext())->range().contains(position)) { DUContext* ret = findContext(position, context.data(topContext())); if (!ret) ret = context.data(topContext()); return ret; } return 0; } bool DUContext::parentContextOf(DUContext* context) const { if (this == context) return true; FOREACH_FUNCTION(const LocalIndexedDUContext& child, d_func()->m_childContexts) { if (child.data(topContext())->parentContextOf(context)) return true; } return false; } QList< QPair > DUContext::allDeclarations(const CursorInRevision& position, const TopDUContext* topContext, bool searchInParents) const { ENSURE_CAN_READ QList< QPair > ret; QHash hadContexts; // Iterate back up the chain mergeDeclarationsInternal(ret, position, hadContexts, topContext ? topContext : this->topContext(), searchInParents); return ret; } QVector DUContext::localDeclarations(const TopDUContext* source) const { Q_UNUSED(source); ENSURE_CAN_READ QMutexLocker lock(&DUContextDynamicData::m_localDeclarationsMutex); QVector ret; FOREACH_FUNCTION(const LocalIndexedDeclaration& decl, d_func()->m_localDeclarations) { ret << decl.data(topContext()); } return ret; } void DUContext::mergeDeclarationsInternal(QList< QPair >& definitions, const CursorInRevision& position, QHash& hadContexts, const TopDUContext* source, bool searchInParents, int currentDepth) const { if((currentDepth > 300 && currentDepth < 1000) || currentDepth > 1300) { kDebug() << "too much depth"; return; } DUCHAIN_D(DUContext); if(hadContexts.contains(this) && !searchInParents) return; if(!hadContexts.contains(this)) { hadContexts[this] = true; if( (type() == DUContext::Namespace || type() == DUContext::Global) && currentDepth < 1000 ) currentDepth += 1000; { QMutexLocker lock(&DUContextDynamicData::m_localDeclarationsMutex); DUContextDynamicData::VisibleDeclarationIterator it(m_dynamicData); while(it) { Declaration* decl = *it; if ( decl && (!position.isValid() || decl->range().start <= position) ) definitions << qMakePair(decl, currentDepth); ++it; } } for(int a = d->m_importedContextsSize()-1; a >= 0; --a) { const Import* import(&d->m_importedContexts()[a]); DUContext* context = import->context(source); while( !context && a > 0 ) { --a; import = &d->m_importedContexts()[a]; context = import->context(source); } if( !context ) break; if(context == this) { kDebug() << "resolved self as import:" << scopeIdentifier(true); continue; } if( position.isValid() && import->position.isValid() && position < import->position ) continue; context->mergeDeclarationsInternal(definitions, CursorInRevision::invalid(), hadContexts, source, searchInParents && context->shouldSearchInParent(InImportedParentContext) && context->parentContext()->type() == DUContext::Helper, currentDepth+1); } } ///Only respect the position if the parent-context is not a class(@todo this is language-dependent) if (parentContext() && searchInParents ) parentContext()->mergeDeclarationsInternal(definitions, parentContext()->type() == DUContext::Class ? parentContext()->range().end : position, hadContexts, source, searchInParents, currentDepth+1); } void DUContext::deleteLocalDeclarations() { ENSURE_CAN_WRITE KDevVarLengthArray declarations; { QMutexLocker lock(&DUContextDynamicData::m_localDeclarationsMutex); FOREACH_FUNCTION(const LocalIndexedDeclaration& decl, d_func()->m_localDeclarations) declarations.append(decl); } TopDUContext* top = topContext(); //If we are deleting something that is not stored to disk, we need to create + delete the declarations, //so their destructor unregisters from the persistent symbol table and from TopDUContextDynamicData FOREACH_ARRAY(const LocalIndexedDeclaration& decl, declarations) if(decl.isLoaded(top) || !top->deleting() || !top->isOnDisk()) delete decl.data(top); } void DUContext::deleteChildContextsRecursively() { ENSURE_CAN_WRITE TopDUContext* top = topContext(); QVector children; FOREACH_FUNCTION(const LocalIndexedDUContext& ctx, d_func()->m_childContexts) children << ctx; //If we are deleting a context that is already stored to disk, we don't need to load not yet loaded child-contexts. //Else we need to, so the declarations are unregistered from symbol-table and from TopDUContextDynamicData in their destructor foreach(const LocalIndexedDUContext &ctx, children) if(ctx.isLoaded(top) || !top->deleting() || !top->isOnDisk()) delete ctx.data(top); } QVector< Declaration * > DUContext::clearLocalDeclarations( ) { QVector< Declaration * > ret = localDeclarations(); foreach (Declaration* dec, ret) dec->setContext(0); return ret; } QualifiedIdentifier DUContext::scopeIdentifier(bool includeClasses) const { ENSURE_CAN_READ QualifiedIdentifier ret; m_dynamicData->scopeIdentifier(includeClasses, ret); return ret; } bool DUContext::equalScopeIdentifier(const DUContext* rhs) const { ENSURE_CAN_READ const DUContext* left = this; const DUContext* right = rhs; while(left || right) { if(!left || !right) return false; if(!(left->d_func()->m_scopeIdentifier == right->d_func()->m_scopeIdentifier)) return false; left = left->parentContext(); right = right->parentContext(); } return true; } void DUContext::setLocalScopeIdentifier(const QualifiedIdentifier & identifier) { ENSURE_CAN_WRITE //Q_ASSERT(d_func()->m_childContexts.isEmpty() && d_func()->m_localDeclarations.isEmpty()); bool wasInSymbolTable = inSymbolTable(); setInSymbolTable(false); d_func_dynamic()->m_scopeIdentifier = identifier; setInSymbolTable(wasInSymbolTable); //DUChain::contextChanged(this, DUChainObserver::Change, DUChainObserver::Identifier); } QualifiedIdentifier DUContext::localScopeIdentifier() const { //ENSURE_CAN_READ Commented out for performance reasons return d_func()->m_scopeIdentifier; } IndexedQualifiedIdentifier DUContext::indexedLocalScopeIdentifier() const { return d_func()->m_scopeIdentifier; } DUContext::ContextType DUContext::type() const { //ENSURE_CAN_READ This is disabled, because type() is called very often while searching, and it costs us performance return d_func()->m_contextType; } void DUContext::setType(ContextType type) { ENSURE_CAN_WRITE d_func_dynamic()->m_contextType = type; //DUChain::contextChanged(this, DUChainObserver::Change, DUChainObserver::ContextType); } QList DUContext::findDeclarations(const Identifier& identifier, const CursorInRevision& position, const TopDUContext* topContext, SearchFlags flags) const { ENSURE_CAN_READ DeclarationList ret; SearchItem::PtrList identifiers; identifiers << SearchItem::Ptr(new SearchItem(QualifiedIdentifier(identifier))); findDeclarationsInternal(identifiers, position.isValid() ? position : range().end, AbstractType::Ptr(), ret, topContext ? topContext : this->topContext(), flags, 0); return ret.toList(); } void DUContext::deleteUse(int index) { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); d->m_usesList().remove(index); } void DUContext::deleteUses() { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); d->m_usesList().clear(); } void DUContext::deleteUsesRecursively() { deleteUses(); FOREACH_FUNCTION(const LocalIndexedDUContext& childContext, d_func()->m_childContexts) childContext.data(topContext())->deleteUsesRecursively(); } bool DUContext::inDUChain() const { if( d_func()->m_anonymousInParent || !m_dynamicData->m_parentContext) return false; TopDUContext* top = topContext(); return top && top->inDUChain(); } DUContext* DUContext::specialize(const IndexedInstantiationInformation& /*specialization*/, const TopDUContext* topContext, int /*upDistance*/) { if(!topContext) return 0; return this; } CursorInRevision DUContext::importPosition(const DUContext* target) const { ENSURE_CAN_READ DUCHAIN_D(DUContext); Import import(const_cast(target), this, CursorInRevision::invalid()); for(unsigned int a = 0; a < d->m_importedContextsSize(); ++a) if(d->m_importedContexts()[a] == import) return d->m_importedContexts()[a].position; return CursorInRevision::invalid(); } QVector DUContext::importedParentContexts() const { ENSURE_CAN_READ QVector ret; FOREACH_FUNCTION(const DUContext::Import& import, d_func()->m_importedContexts) ret << import; return ret; } void DUContext::applyAliases(const SearchItem::PtrList& baseIdentifiers, SearchItem::PtrList& identifiers, const CursorInRevision& position, bool canBeNamespace, bool onlyImports) const { DeclarationList imports; findLocalDeclarationsInternal(globalImportIdentifier(), position, AbstractType::Ptr(), imports, topContext(), DUContext::NoFiltering); if(imports.isEmpty() && onlyImports) { identifiers = baseIdentifiers; return; } FOREACH_ARRAY( const SearchItem::Ptr& identifier, baseIdentifiers ) { bool addUnmodified = true; if( !identifier->isExplicitlyGlobal ) { if( !imports.isEmpty() ) { //We have namespace-imports. FOREACH_ARRAY( Declaration* importDecl, imports ) { //Search for the identifier with the import-identifier prepended if(dynamic_cast(importDecl)) { NamespaceAliasDeclaration* alias = static_cast(importDecl); identifiers.append( SearchItem::Ptr( new SearchItem( alias->importIdentifier(), identifier ) ) ) ; }else{ kDebug() << "Declaration with namespace alias identifier has the wrong type" << importDecl->url().str() << importDecl->range().castToSimpleRange().textRange(); } } } if( !identifier->isEmpty() && (identifier->hasNext() || canBeNamespace) ) { DeclarationList aliases; findLocalDeclarationsInternal(identifier->identifier, position, AbstractType::Ptr(), imports, 0, DUContext::NoFiltering); if(!aliases.isEmpty()) { //The first part of the identifier has been found as a namespace-alias. //In c++, we only need the first alias. However, just to be correct, follow them all for now. FOREACH_ARRAY( Declaration* aliasDecl, aliases ) { if(!dynamic_cast(aliasDecl)) continue; addUnmodified = false; //The un-modified identifier can be ignored, because it will be replaced with the resolved alias NamespaceAliasDeclaration* alias = static_cast(aliasDecl); //Create an identifier where namespace-alias part is replaced with the alias target identifiers.append( SearchItem::Ptr( new SearchItem( alias->importIdentifier(), identifier->next ) ) ) ; } } } } if( addUnmodified ) identifiers.append(identifier); } } void DUContext::applyUpwardsAliases(SearchItem::PtrList& identifiers, const TopDUContext* /*source*/) const { if(type() == Namespace) { QualifiedIdentifier localId = d_func()->m_scopeIdentifier; if(localId.isEmpty()) return; //Make sure we search for the items in all namespaces of the same name, by duplicating each one with the namespace-identifier prepended. //We do this by prepending items to the current identifiers that equal the local scope identifier. SearchItem::Ptr newItem( new SearchItem(localId) ); //This will exclude explictly global identifiers newItem->addToEachNode( identifiers ); if(!newItem->next.isEmpty()) { //Prepend the full scope before newItem DUContext* parent = m_dynamicData->m_parentContext.data(); while(parent) { newItem = SearchItem::Ptr( new SearchItem(parent->d_func()->m_scopeIdentifier, newItem) ); parent = parent->m_dynamicData->m_parentContext.data(); } newItem->isExplicitlyGlobal = true; identifiers.insert(0, newItem); } } } bool DUContext::shouldSearchInParent(SearchFlags flags) const { return (parentContext() && parentContext()->type() == DUContext::Helper && (flags & InImportedParentContext)) || !(flags & InImportedParentContext); } const Use* DUContext::uses() const { ENSURE_CAN_READ return d_func()->m_uses(); } bool DUContext::declarationHasUses(Declaration* decl) { return DUChain::uses()->hasUses(decl->id()); } int DUContext::usesCount() const { return d_func()->m_usesSize(); } bool usesRangeLessThan(const Use& left, const Use& right) { return left.m_range.start < right.m_range.start; } int DUContext::createUse(int declarationIndex, const RangeInRevision& range, int insertBefore) { DUCHAIN_D_DYNAMIC(DUContext); ENSURE_CAN_WRITE Use use(range, declarationIndex); if(insertBefore == -1) { //Find position where to insert const unsigned int size = d->m_usesSize(); const Use* uses = d->m_uses(); const Use* lowerBound = qLowerBound(uses, uses + size, use, usesRangeLessThan); insertBefore = lowerBound - uses; // comment out to test this: /* unsigned int a = 0; for(; a < size && range.start > uses[a].m_range.start; ++a) { } Q_ASSERT(a == insertBefore); */ } d->m_usesList().insert(insertBefore, use); return insertBefore; } void DUContext::changeUseRange(int useIndex, const RangeInRevision& range) { ENSURE_CAN_WRITE d_func_dynamic()->m_usesList()[useIndex].m_range = range; } void DUContext::setUseDeclaration(int useNumber, int declarationIndex) { ENSURE_CAN_WRITE d_func_dynamic()->m_usesList()[useNumber].m_declarationIndex = declarationIndex; } DUContext * DUContext::findContextAt(const CursorInRevision & position, bool includeRightBorder) const { ENSURE_CAN_READ // kDebug() << "searchign" << position.textCursor() << "in:" << scopeIdentifier(true).toString() << range().textRange() << includeRightBorder; if (!range().contains(position) && (!includeRightBorder || range().end != position)) { // kDebug() << "mismatch"; return 0; } for(int a = int(d_func()->m_childContextsSize())-1; a >= 0; --a) if (DUContext* specific = d_func()->m_childContexts()[a].data(topContext())->findContextAt(position, includeRightBorder)) return specific; return const_cast(this); } Declaration * DUContext::findDeclarationAt(const CursorInRevision & position) const { ENSURE_CAN_READ if (!range().contains(position)) return 0; FOREACH_FUNCTION(const LocalIndexedDeclaration& child, d_func()->m_localDeclarations) if (child.data(topContext())->range().contains(position)) return child.data(topContext()); return 0; } DUContext* DUContext::findContextIncluding(const RangeInRevision& range) const { ENSURE_CAN_READ if (!this->range().contains(range)) return 0; FOREACH_FUNCTION(const LocalIndexedDUContext& child, d_func()->m_childContexts) if (DUContext* specific = child.data(topContext())->findContextIncluding(range)) return specific; return const_cast(this); } int DUContext::findUseAt(const CursorInRevision & position) const { ENSURE_CAN_READ if (!range().contains(position)) return -1; for(unsigned int a = 0; a < d_func()->m_usesSize(); ++a) if (d_func()->m_uses()[a].m_range.contains(position)) return a; return -1; } bool DUContext::inSymbolTable() const { return d_func()->m_inSymbolTable; } void DUContext::setInSymbolTable(bool inSymbolTable) { d_func_dynamic()->m_inSymbolTable = inSymbolTable; } // kate: indent-width 2; void DUContext::clearImportedParentContexts() { ENSURE_CAN_WRITE DUCHAIN_D_DYNAMIC(DUContext); while( d->m_importedContextsSize() != 0 ) { DUContext* ctx = d->m_importedContexts()[0].context(0, false); if(ctx) ctx->m_dynamicData->removeImportedChildContext(this); d->m_importedContextsList().removeOne(d->m_importedContexts()[0]); } } void DUContext::cleanIfNotEncountered(const QSet& encountered) { ENSURE_CAN_WRITE // It may happen that the deletion of one declaration triggers the deletion of another one // Therefore we copy the list of indexed declarations and work on those. Indexed declarations // will return zero for already deleted declarations. KDevVarLengthArray declarationsCopy; { QMutexLocker lock(&DUContextDynamicData::m_localDeclarationsMutex); declarationsCopy = d_func_dynamic()->m_localDeclarationsList(); } FOREACH_ARRAY(const LocalIndexedDeclaration& indexedDec, declarationsCopy) { Declaration* dec = indexedDec.data(topContext()); if (dec && !encountered.contains(dec) && (!dec->isAutoDeclaration() || !dec->hasUses())) delete dec; } //Copy since the array may change during the iteration KDevVarLengthArray childrenCopy = d_func_dynamic()->m_childContextsList(); FOREACH_ARRAY(const LocalIndexedDUContext& childContext, childrenCopy) if (!encountered.contains(childContext.data(topContext()))) delete childContext.data(topContext()); } TopDUContext* DUContext::topContext() const { return m_dynamicData->m_topContext; } QWidget* DUContext::createNavigationWidget(Declaration* decl, TopDUContext* topContext, const QString& htmlPrefix, const QString& htmlSuffix) const { if (decl) { AbstractNavigationWidget* widget = new AbstractNavigationWidget; AbstractDeclarationNavigationContext* context = new AbstractDeclarationNavigationContext(DeclarationPointer(decl), TopDUContextPointer(topContext)); context->setPrefixSuffix(htmlPrefix, htmlSuffix); widget->setContext(NavigationContextPointer(context)); return widget; } else { return 0; } } void DUContext::squeeze() { FOREACH_FUNCTION(const LocalIndexedDUContext& child, d_func()->m_childContexts) child.data(topContext())->squeeze(); } QList allUses(DUContext* context, int declarationIndex, bool noEmptyUses) { QList ret; for(int a = 0; a < context->usesCount(); ++a) if(context->uses()[a].m_declarationIndex == declarationIndex) if(!noEmptyUses || !context->uses()[a].m_range.isEmpty()) ret << context->uses()[a].m_range; foreach(DUContext* child, context->childContexts()) ret += allUses(child, declarationIndex, noEmptyUses); return ret; } DUContext::SearchItem::SearchItem(const QualifiedIdentifier& id, Ptr nextItem, int start) : isExplicitlyGlobal(start == 0 ? id.explicitlyGlobal() : false) { if(!id.isEmpty()) { if(id.count() > start) identifier = id.at(start); if(id.count() > start+1) addNext(Ptr( new SearchItem(id, nextItem, start+1) )); else if(nextItem) next.append(nextItem); }else if(nextItem) { ///If there is no prefix, just copy nextItem isExplicitlyGlobal = nextItem->isExplicitlyGlobal; identifier = nextItem->identifier; next = nextItem->next; } } DUContext::SearchItem::SearchItem(const QualifiedIdentifier& id, const PtrList& nextItems, int start) : isExplicitlyGlobal(start == 0 ? id.explicitlyGlobal() : false) { if(id.count() > start) identifier = id.at(start); if(id.count() > start+1) addNext(Ptr( new SearchItem(id, nextItems, start+1) )); else next = nextItems; } DUContext::SearchItem::SearchItem(bool explicitlyGlobal, Identifier id, const PtrList& nextItems) : isExplicitlyGlobal(explicitlyGlobal) , identifier(id) , next(nextItems) { } DUContext::SearchItem::SearchItem(bool explicitlyGlobal, Identifier id, Ptr nextItem) : isExplicitlyGlobal(explicitlyGlobal) , identifier(id) { next.append(nextItem); } bool DUContext::SearchItem::match(const QualifiedIdentifier& id, int offset) const { if(id.isEmpty()) { if(identifier.isEmpty() && next.isEmpty()) return true; else return false; } if(id.at(offset) != identifier) //The identifier is different return false; if(offset == id.count()-1) { if(next.isEmpty()) return true; //match else return false; //id is too short } for(int a = 0; a < next.size(); ++a) if(next[a]->match(id, offset+1)) return true; return false; } bool DUContext::SearchItem::isEmpty() const { return identifier.isEmpty(); } bool DUContext::SearchItem::hasNext() const { return !next.isEmpty(); } QList DUContext::SearchItem::toList(const QualifiedIdentifier& prefix) const { QList ret; QualifiedIdentifier id = prefix; if(id.isEmpty()) id.setExplicitlyGlobal(isExplicitlyGlobal); if(!identifier.isEmpty()) id.push(identifier); if(next.isEmpty()) { ret << id; } else { for(int a = 0; a < next.size(); ++a) ret += next[a]->toList(id); } return ret; } void DUContext::SearchItem::addNext(SearchItem::Ptr other) { next.append(other); } void DUContext::SearchItem::addToEachNode(SearchItem::Ptr other) { if(other->isExplicitlyGlobal) return; next.append(other); for(int a = 0; a < next.size()-1; ++a) next[a]->addToEachNode(other); } void DUContext::SearchItem::addToEachNode(SearchItem::PtrList other) { int added = 0; FOREACH_ARRAY(const SearchItem::Ptr& o, other) { if(!o->isExplicitlyGlobal) { next.append(o); ++added; } } for(int a = 0; a < next.size()-added; ++a) next[a]->addToEachNode(other); } DUContext::Import::Import(DUContext* _context, const DUContext* importer, const CursorInRevision& _position) : position(_position) { if(_context && _context->owner() && (_context->owner()->specialization().index() || (importer && importer->topContext() != _context->topContext()))) { m_declaration = _context->owner()->id(); }else{ m_context = _context; } } DUContext::Import::Import(const DeclarationId& id, const CursorInRevision& _position) : position(_position) { m_declaration = id; } DUContext* DUContext::Import::context(const TopDUContext* topContext, bool instantiateIfRequired) const { if(m_declaration.isValid()) { Declaration* decl = m_declaration.getDeclaration(topContext, instantiateIfRequired); //This first case rests on the assumption that no context will ever import a function's expression context //More accurately, that no specialized or cross-topContext imports will, but if the former assumption fails the latter will too if (AbstractFunctionDeclaration *functionDecl = dynamic_cast(decl)) { Q_ASSERT(functionDecl->internalFunctionContext()); return functionDecl->internalFunctionContext(); } else if(decl) return decl->logicalInternalContext(topContext); else return 0; }else{ return m_context.data(); } } bool DUContext::Import::isDirect() const { return m_context.isValid(); } void DUContext::visit(DUChainVisitor& visitor) { visitor.visit(this); TopDUContext* top = topContext(); { QMutexLocker lock(&DUContextDynamicData::m_localDeclarationsMutex); FOREACH_FUNCTION(const LocalIndexedDeclaration& decl, d_func()->m_localDeclarations) visitor.visit(decl.data(top)); } FOREACH_FUNCTION(const LocalIndexedDUContext& ctx, d_func()->m_childContexts) ctx.data(top)->visit(visitor); } } // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/projectmanagerview/projectbuildsetwidget.cpp b/plugins/projectmanagerview/projectbuildsetwidget.cpp index 1f5c636166..264b9a4ae5 100644 --- a/plugins/projectmanagerview/projectbuildsetwidget.cpp +++ b/plugins/projectmanagerview/projectbuildsetwidget.cpp @@ -1,270 +1,269 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "projectbuildsetwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "projectmanagerviewplugin.h" #include "projectmanagerview.h" #include "ui_projectbuildsetwidget.h" #include -#include #include #include #include ProjectBuildSetWidget::ProjectBuildSetWidget( QWidget* parent ) : QWidget( parent ), m_view( 0 ), m_ui( new Ui::ProjectBuildSetWidget ) { m_ui->setupUi( this ); m_ui->addItemButton->setIcon( KIcon( "list-add" ) ); connect( m_ui->addItemButton, SIGNAL(clicked()), this, SLOT(addItems()) ); m_ui->removeItemButton->setIcon( KIcon( "list-remove" ) ); connect( m_ui->removeItemButton, SIGNAL(clicked()), this, SLOT(removeItems()) ); m_ui->upButton->setIcon( KIcon( "go-up" ) ); connect( m_ui->upButton, SIGNAL(clicked()), SLOT(moveUp()) ); m_ui->downButton->setIcon( KIcon( "go-down" ) ); connect( m_ui->downButton, SIGNAL(clicked()), SLOT(moveDown()) ); m_ui->topButton->setIcon( KIcon( "go-top" ) ); connect( m_ui->topButton, SIGNAL(clicked()), SLOT(moveToTop()) ); m_ui->bottomButton->setIcon( KIcon( "go-bottom" ) ); connect( m_ui->bottomButton, SIGNAL(clicked()), SLOT(moveToBottom()) ); m_ui->itemView->setContextMenuPolicy( Qt::CustomContextMenu ); connect( m_ui->itemView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(showContextMenu(QPoint)) ); layout()->setMargin(0); } void ProjectBuildSetWidget::setProjectView( ProjectManagerView* view ) { m_view = view; m_ui->itemView->setModel( KDevelop::ICore::self()->projectController()->buildSetModel() ); connect( m_ui->itemView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(selectionChanged()) ); } void ProjectBuildSetWidget::selectionChanged() { QModelIndexList selectedRows = m_ui->itemView->selectionModel()->selectedRows(); kDebug() << "checking selectionmodel:" << selectedRows; m_ui->removeItemButton->setEnabled( !selectedRows.isEmpty() ); m_ui->addItemButton->setEnabled( !m_view->selectedItems().isEmpty() ); bool enableUp = selectedRows.count() > 0 && selectedRows.first().row() != 0; bool enableDown = selectedRows.count() > 0 && selectedRows.last().row() != m_ui->itemView->model()->rowCount() - 1; m_ui->upButton->setEnabled( enableUp ); m_ui->downButton->setEnabled( enableDown ); m_ui->bottomButton->setEnabled( enableDown ); m_ui->topButton->setEnabled( enableUp ); } ProjectBuildSetWidget::~ProjectBuildSetWidget() { delete m_ui; } //TODO test whether this could be replaced by projecttreeview.cpp::popupContextMenu_appendActions void showContextMenu_appendActions(QMenu& menu, const QList& actions) { menu.addSeparator(); foreach( QAction* act, actions ) { menu.addAction(act); } } void ProjectBuildSetWidget::showContextMenu( const QPoint& p ) { if( m_ui->itemView->selectionModel()->selectedRows().isEmpty() ) return; QList itemlist; if(m_ui->itemView->selectionModel()->selectedRows().count() == 1) { KDevelop::ProjectBuildSetModel* buildSet = KDevelop::ICore::self()->projectController()->buildSetModel(); int row = m_ui->itemView->selectionModel()->selectedRows()[0].row(); if(row < buildSet->items().size()) { KDevelop::ProjectBaseItem* item = buildSet->items()[row].findItem(); if(item) itemlist << item; } } KMenu m; m.setTitle( i18n("Build Set") ); m.addAction( KIcon("list-remove"), i18n( "Remove From Build Set" ), this, SLOT(removeItems()) ); if( !itemlist.isEmpty() ) { KDevelop::ProjectItemContext context(itemlist); QList extensions = KDevelop::ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( &context ); QList buildActions; QList vcsActions; QList extActions; QList projectActions; QList fileActions; QList runActions; foreach( const KDevelop::ContextMenuExtension& ext, extensions ) { buildActions += ext.actions(KDevelop::ContextMenuExtension::BuildGroup); fileActions += ext.actions(KDevelop::ContextMenuExtension::FileGroup); projectActions += ext.actions(KDevelop::ContextMenuExtension::ProjectGroup); vcsActions += ext.actions(KDevelop::ContextMenuExtension::VcsGroup); extActions += ext.actions(KDevelop::ContextMenuExtension::ExtensionGroup); runActions += ext.actions(KDevelop::ContextMenuExtension::RunGroup); } showContextMenu_appendActions(m, buildActions); showContextMenu_appendActions(m, runActions); showContextMenu_appendActions(m, fileActions); showContextMenu_appendActions(m, vcsActions); showContextMenu_appendActions(m, extActions); showContextMenu_appendActions(m, projectActions); } m.exec( m_ui->itemView->viewport()->mapToGlobal( p ) ); } void ProjectBuildSetWidget::addItems() { foreach( KDevelop::ProjectBaseItem* item, m_view->selectedItems() ) { KDevelop::ICore::self()->projectController()->buildSetModel()->addProjectItem( item ); } } void ProjectBuildSetWidget::removeItems() { // We only support contigous selection, so we only need to remove the first range QItemSelectionRange range = m_ui->itemView->selectionModel()->selection().first(); int top = range.top(); kDebug() << "removing:" << range.top() << range.height(); KDevelop::ProjectBuildSetModel* buildSet = KDevelop::ICore::self()->projectController()->buildSetModel(); buildSet->removeRows( range.top(), range.height() ); top = qMin( top, buildSet->rowCount() - 1 ); QModelIndex sidx = buildSet->index( top, 0 ); QModelIndex eidx = buildSet->index( top, buildSet->columnCount() - 1 ); m_ui->itemView->selectionModel()->select( QItemSelection( sidx, eidx ), QItemSelectionModel::ClearAndSelect ); m_ui->itemView->selectionModel()->setCurrentIndex( sidx, QItemSelectionModel::Current ); } void ProjectBuildSetWidget::moveDown() { // We only support contigous selection, so we only need to remove the first range QItemSelectionRange range = m_ui->itemView->selectionModel()->selection().first(); int top = range.top(), height = range.height(); KDevelop::ProjectBuildSetModel* buildSet = KDevelop::ICore::self()->projectController()->buildSetModel(); buildSet->moveRowsDown( top, height ); int columnCount = buildSet->columnCount(); QItemSelection newrange( buildSet->index( top + 1, 0 ), buildSet->index( top + height, columnCount - 1 ) ); m_ui->itemView->selectionModel()->select( newrange, QItemSelectionModel::ClearAndSelect ); m_ui->itemView->selectionModel()->setCurrentIndex( newrange.first().topLeft(), QItemSelectionModel::Current ); } void ProjectBuildSetWidget::moveToBottom() { // We only support contigous selection, so we only need to remove the first range QItemSelectionRange range = m_ui->itemView->selectionModel()->selection().first(); int top = range.top(), height = range.height(); KDevelop::ProjectBuildSetModel* buildSet = KDevelop::ICore::self()->projectController()->buildSetModel(); buildSet->moveRowsToBottom( top, height ); int rowCount = buildSet->rowCount(); int columnCount = buildSet->columnCount(); QItemSelection newrange( buildSet->index( rowCount - height, 0 ), buildSet->index( rowCount - 1, columnCount - 1 ) ); m_ui->itemView->selectionModel()->select( newrange, QItemSelectionModel::ClearAndSelect ); m_ui->itemView->selectionModel()->setCurrentIndex( newrange.first().topLeft(), QItemSelectionModel::Current ); } void ProjectBuildSetWidget::moveUp() { // We only support contigous selection, so we only need to remove the first range QItemSelectionRange range = m_ui->itemView->selectionModel()->selection().first(); int top = range.top(), height = range.height(); KDevelop::ProjectBuildSetModel* buildSet = KDevelop::ICore::self()->projectController()->buildSetModel(); buildSet->moveRowsUp( top, height ); int columnCount = buildSet->columnCount(); QItemSelection newrange( buildSet->index( top - 1, 0 ), buildSet->index( top - 2 + height, columnCount - 1 ) ); m_ui->itemView->selectionModel()->select( newrange, QItemSelectionModel::ClearAndSelect ); m_ui->itemView->selectionModel()->setCurrentIndex( newrange.first().topLeft(), QItemSelectionModel::Current ); } void ProjectBuildSetWidget::moveToTop() { // We only support contigous selection, so we only need to remove the first range QItemSelectionRange range = m_ui->itemView->selectionModel()->selection().first(); int top = range.top(), height = range.height(); KDevelop::ProjectBuildSetModel* buildSet = KDevelop::ICore::self()->projectController()->buildSetModel(); buildSet->moveRowsToTop( top, height ); int columnCount = buildSet->columnCount(); QItemSelection newrange( buildSet->index( 0, 0 ), buildSet->index( height - 1, columnCount - 1 ) ); m_ui->itemView->selectionModel()->select( newrange, QItemSelectionModel::ClearAndSelect ); m_ui->itemView->selectionModel()->setCurrentIndex( newrange.first().topLeft(), QItemSelectionModel::Current ); } diff --git a/plugins/testview/testviewplugin.cpp b/plugins/testview/testviewplugin.cpp index 2518afb224..5d7f2cb07e 100644 --- a/plugins/testview/testviewplugin.cpp +++ b/plugins/testview/testviewplugin.cpp @@ -1,118 +1,117 @@ /* This file is part of KDevelop Copyright 2012 Miha ?an?ula This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "testviewplugin.h" #include "testview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include K_PLUGIN_FACTORY(TestViewFactory, registerPlugin(); ) K_EXPORT_PLUGIN(TestViewFactory(KAboutData("kdevtestview","kdevtestview", ki18n("Unit Test View"), "0.1", ki18n("Lets you see and run unit tests"), KAboutData::License_GPL))) using namespace KDevelop; class TestToolViewFactory: public KDevelop::IToolViewFactory { public: TestToolViewFactory( TestViewPlugin *plugin ): mplugin( plugin ) {} virtual QWidget* create( QWidget *parent = 0 ) { return new TestView( mplugin, parent ); } virtual Qt::DockWidgetArea defaultPosition() { return Qt::LeftDockWidgetArea; } virtual QString id() const { return "org.kdevelop.TestView"; } virtual QList< QAction* > contextMenuActions(QWidget* viewWidget) const { return qobject_cast(viewWidget)->contextMenuActions(); } private: TestViewPlugin *mplugin; }; TestViewPlugin::TestViewPlugin(QObject* parent, const QVariantList& args): IPlugin(TestViewFactory::componentData(), parent) { Q_UNUSED(args) KAction* runAll = new KAction( KIcon("system-run"), i18n("Run All Tests"), this ); connect(runAll, SIGNAL(triggered(bool)), SLOT(runAllTests())); actionCollection()->addAction("run_all_tests", runAll); setXMLFile("kdevtestview.rc"); m_viewFactory = new TestToolViewFactory(this); core()->uiController()->addToolView(i18n("Unit Tests"), m_viewFactory); } TestViewPlugin::~TestViewPlugin() { } void TestViewPlugin::unload() { core()->uiController()->removeToolView(m_viewFactory); } void TestViewPlugin::runAllTests() { QString jobName = "Run %1 tests in %2"; ITestController* tc = core()->testController(); foreach (IProject* project, core()->projectController()->projects()) { QList jobs; foreach (ITestSuite* suite, tc->testSuitesForProject(project)) { if (KJob* job = suite->launchAllCases(ITestSuite::Silent)) { jobs << job; } } if (!jobs.isEmpty()) { KDevelop::ExecuteCompositeJob* compositeJob = new KDevelop::ExecuteCompositeJob(this, jobs); compositeJob->setObjectName(i18np("Run 1 test in %2", "Run %1 tests in %2", jobs.size(), project->name())); core()->runController()->registerJob(compositeJob); } } } diff --git a/project/tests/projectmodeltest.cpp b/project/tests/projectmodeltest.cpp index 597c35b1c1..c8cadb2bfb 100644 --- a/project/tests/projectmodeltest.cpp +++ b/project/tests/projectmodeltest.cpp @@ -1,615 +1,614 @@ /*************************************************************************** * Copyright 2010 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "projectmodeltest.h" #include #include #include #include #include #include #include #include #include -#include #include #include #include using KDevelop::ProjectModel; using KDevelop::ProjectBaseItem; using KDevelop::ProjectFolderItem; using KDevelop::ProjectFileItem; using KDevelop::ProjectExecutableTargetItem; using KDevelop::ProjectLibraryTargetItem; using KDevelop::ProjectTargetItem; using KDevelop::ProjectBuildFolderItem; using KDevelop::TestProject; class AddItemThread : public QThread { Q_OBJECT public: AddItemThread( ProjectBaseItem* _parentItem, QObject* parent = 0 ) : QThread( parent ), parentItem( _parentItem ) { } virtual void run() { this->sleep( 1 ); KUrl url = parentItem->url(); url.addPath("folder1"); ProjectFolderItem* folder = new ProjectFolderItem( 0, url, parentItem ); url.addPath( "file1" ); new ProjectFileItem( 0, url, folder ); emit addedItems(); } signals: void addedItems(); private: ProjectBaseItem* parentItem; }; class SignalReceiver : public QObject { Q_OBJECT public: SignalReceiver(ProjectModel* _model, QObject* parent = 0) : QObject(parent), model( _model ) { } QThread* threadOfSignalEmission() const { return threadOfReceivedSignal; } private slots: void rowsInserted( const QModelIndex&, int, int ) { threadOfReceivedSignal = QThread::currentThread(); } private: QThread* threadOfReceivedSignal; ProjectModel* model; }; void debugItemModel(QAbstractItemModel* m, const QModelIndex& parent=QModelIndex(), int depth=0) { Q_ASSERT(m); qDebug() << QByteArray(depth*2, '-') << m->data(parent).toString(); for(int i=0; irowCount(parent); i++) { debugItemModel(m, m->index(i, 0, parent), depth+1); } } void ProjectModelTest::initTestCase() { KDevelop::AutoTestShell::init(); KDevelop::TestCore::initialize(KDevelop::Core::NoUi); qRegisterMetaType("QModelIndex"); model = KDevelop::ICore::self()->projectController()->projectModel(); new ModelTest( model, this ); proxy = new ProjectProxyModel( model ); new ModelTest(proxy, proxy); proxy->setSourceModel(model); } void ProjectModelTest::init() { model->clear(); } void ProjectModelTest::cleanupTestCase() { KDevelop::TestCore::shutdown(); } void ProjectModelTest::testCreateFileSystemItems() { QFETCH( int, itemType ); QFETCH( KUrl, itemUrl ); QFETCH( KUrl, expectedItemUrl ); QFETCH( QString, expectedItemText ); QFETCH( QStringList, expectedItemPath ); QFETCH( int, expectedItemRow ); ProjectBaseItem* newitem = 0; switch( itemType ) { case ProjectBaseItem::Folder: newitem = new ProjectFolderItem( 0, itemUrl ); break; case ProjectBaseItem::BuildFolder: newitem = new ProjectBuildFolderItem( 0, itemUrl ); break; case ProjectBaseItem::File: newitem = new ProjectFileItem( 0, itemUrl ); break; } int origRowCount = model->rowCount(); model->appendRow( newitem ); QCOMPARE( model->rowCount(), origRowCount+1 ); QCOMPARE( newitem->row(), expectedItemRow ); QModelIndex idx = model->index( expectedItemRow, 0, QModelIndex() ); QVERIFY( model->itemFromIndex( idx ) ); QCOMPARE( model->itemFromIndex( idx ), newitem ); QCOMPARE( newitem->text(), expectedItemText ); QCOMPARE( newitem->url(), expectedItemUrl ); if( itemType == ProjectBaseItem::File ) { QCOMPARE( dynamic_cast( newitem )->fileName(), expectedItemText ); } if( itemType == ProjectBaseItem::Folder || itemType == ProjectBaseItem::BuildFolder ) { QCOMPARE( dynamic_cast( newitem )->folderName(), expectedItemText ); } QCOMPARE( newitem->type(), itemType ); QCOMPARE( model->data( idx ).toString(), expectedItemText ); QCOMPARE( model->indexFromItem( newitem ), idx ); QCOMPARE( model->pathFromIndex( idx ), expectedItemPath ); QCOMPARE( model->pathToIndex( expectedItemPath ), idx ); } void ProjectModelTest::testCreateFileSystemItems_data() { QTest::addColumn( "itemType" ); QTest::addColumn( "itemUrl" ); QTest::addColumn( "expectedItemUrl" ); QTest::addColumn( "expectedItemText" ); QTest::addColumn( "expectedItemPath" ); QTest::addColumn( "expectedItemRow" ); QTest::newRow("RootFolder") << (int)ProjectBaseItem::Folder << KUrl("file:///rootdir") << KUrl("file:///rootdir/") << QString::fromLatin1("rootdir") << ( QStringList() << "rootdir" ) << 0; QTest::newRow("RootBuildFolder") << (int)ProjectBaseItem::BuildFolder << KUrl("file:///rootdir") << KUrl("file:///rootdir/") << QString::fromLatin1("rootdir") << ( QStringList() << "rootdir" ) << 0; QTest::newRow("RootFile") << (int)ProjectBaseItem::File << KUrl("file:///rootfile") << KUrl("file:///rootfile") << QString::fromLatin1("rootfile") << ( QStringList() << "rootfile" ) << 0; } void ProjectModelTest::testCreateTargetItems() { QFETCH( int, itemType ); QFETCH( QString, itemText ); QFETCH( QString, expectedItemText ); QFETCH( QStringList, expectedItemPath ); QFETCH( int, expectedItemRow ); ProjectBaseItem* newitem = 0; switch( itemType ) { case ProjectBaseItem::Target: newitem = new ProjectTargetItem( 0, itemText ); break; case ProjectBaseItem::LibraryTarget: newitem = new ProjectLibraryTargetItem( 0, itemText ); break; } int origRowCount = model->rowCount(); model->appendRow( newitem ); QCOMPARE( model->rowCount(), origRowCount+1 ); QCOMPARE( newitem->row(), expectedItemRow ); QModelIndex idx = model->index( expectedItemRow, 0, QModelIndex() ); QVERIFY( model->itemFromIndex( idx ) ); QCOMPARE( model->itemFromIndex( idx ), newitem ); QCOMPARE( newitem->text(), expectedItemText ); QCOMPARE( newitem->type(), itemType ); QCOMPARE( model->data( idx ).toString(), expectedItemText ); QCOMPARE( model->indexFromItem( newitem ), idx ); QCOMPARE( model->pathFromIndex( idx ), expectedItemPath ); QCOMPARE( model->pathToIndex( expectedItemPath ), idx ); } void ProjectModelTest::testCreateTargetItems_data() { QTest::addColumn( "itemType" ); QTest::addColumn( "itemText" ); QTest::addColumn( "expectedItemText" ); QTest::addColumn( "expectedItemPath" ); QTest::addColumn( "expectedItemRow" ); QTest::newRow("RootTarget") << (int)ProjectBaseItem::Target << "target" << QString::fromLatin1("target") << ( QStringList() << "target" ) << 0; QTest::newRow("RootLibraryTarget") << (int)ProjectBaseItem::LibraryTarget << "libtarget" << QString::fromLatin1("libtarget") << ( QStringList() << "libtarget" ) << 0; } void ProjectModelTest::testChangeWithProxyModel() { QSortFilterProxyModel* proxy = new QSortFilterProxyModel( this ); proxy->setSourceModel( model ); ProjectFolderItem* root = new ProjectFolderItem( 0, KUrl("file:///folder1") ); root->appendRow( new ProjectFileItem( 0, KUrl("file:///folder1/file1") ) ); model->appendRow( root ); QCOMPARE( model->rowCount(), 1 ); QCOMPARE( proxy->rowCount(), 1 ); model->removeRow( 0 ); QCOMPARE( model->rowCount(), 0 ); QCOMPARE( proxy->rowCount(), 0 ); } void ProjectModelTest::testCreateSimpleHierarchy() { QString folderName = "rootfolder"; QString fileName = "file"; QString targetName = "testtarged"; QString cppFileName = "file.cpp"; ProjectFolderItem* rootFolder = new ProjectFolderItem( 0, KUrl("file:///"+folderName) ); QCOMPARE(rootFolder->baseName(), folderName); ProjectFileItem* file = new ProjectFileItem( 0, KUrl("file:///"+folderName+"/"+fileName), rootFolder ); QCOMPARE(file->baseName(), fileName); ProjectTargetItem* target = new ProjectTargetItem( 0, targetName ); rootFolder->appendRow( target ); ProjectFileItem* targetfile = new ProjectFileItem( 0, KUrl("file:///"+folderName+"/"+cppFileName), target ); model->appendRow( rootFolder ); QCOMPARE( model->rowCount(), 1 ); QModelIndex folderIdx = model->index( 0, 0, QModelIndex() ); QCOMPARE( model->data( folderIdx ).toString(), folderName ); QCOMPARE( model->rowCount( folderIdx ), 2 ); QCOMPARE( model->itemFromIndex( folderIdx ), rootFolder ); QVERIFY( rootFolder->hasFileOrFolder( fileName ) ); QModelIndex fileIdx = model->index( 0, 0, folderIdx ); QCOMPARE( model->data( fileIdx ).toString(), fileName ); QCOMPARE( model->rowCount( fileIdx ), 0 ); QCOMPARE( model->itemFromIndex( fileIdx ), file ); QModelIndex targetIdx = model->index( 1, 0, folderIdx ); QCOMPARE( model->data( targetIdx ).toString(), targetName ); QCOMPARE( model->rowCount( targetIdx ), 1 ); QCOMPARE( model->itemFromIndex( targetIdx ), target ); QModelIndex targetFileIdx = model->index( 0, 0, targetIdx ); QCOMPARE( model->data( targetFileIdx ).toString(), cppFileName ); QCOMPARE( model->rowCount( targetFileIdx ), 0 ); QCOMPARE( model->itemFromIndex( targetFileIdx ), targetfile ); rootFolder->removeRow( 1 ); QCOMPARE( model->rowCount( folderIdx ), 1 ); delete file; file = 0; // Check that we also find a folder with the fileName new ProjectFolderItem( 0, fileName, rootFolder ); QVERIFY( rootFolder->hasFileOrFolder( fileName ) ); delete rootFolder; QCOMPARE( model->rowCount(), 0 ); } void ProjectModelTest::testItemSanity() { ProjectBaseItem* parent = new ProjectBaseItem( 0, "test" ); ProjectBaseItem* child = new ProjectBaseItem( 0, "test", parent ); ProjectBaseItem* child2 = new ProjectBaseItem( 0, "ztest", parent ); ProjectFileItem* child3 = new ProjectFileItem( 0, KUrl("file:///bcd"), parent ); ProjectFileItem* child4 = new ProjectFileItem( 0, KUrl("file:///abcd"), parent ); // Just some basic santiy checks on the API QCOMPARE( parent->child( 0 ), child ); QCOMPARE( parent->row(), -1 ); QVERIFY( !parent->child( -1 ) ); QVERIFY( !parent->file() ); QVERIFY( !parent->folder() ); QVERIFY( !parent->project() ); QVERIFY( !parent->child( parent->rowCount() ) ); QCOMPARE( parent->iconName(), QString() ); QCOMPARE( parent->index(), QModelIndex() ); QCOMPARE( child->type(), (int)ProjectBaseItem::BaseItem ); QCOMPARE( child->lessThan( child2 ), true ); QCOMPARE( child3->lessThan( child4 ), false ); // Check that model is properly emitting data-changes model->appendRow( parent ); QCOMPARE( parent->index(), model->index(0, 0, QModelIndex()) ); QSignalSpy s( model, SIGNAL(dataChanged(QModelIndex,QModelIndex)) ); parent->setUrl( KUrl("file:///newtest") ); QCOMPARE( s.count(), 1 ); QCOMPARE( model->data( parent->index() ).toString(), QString("newtest") ); parent->removeRow( child->row() ); } void ProjectModelTest::testTakeRow() { ProjectBaseItem* parent = new ProjectBaseItem( 0, "test" ); ProjectBaseItem* child = new ProjectBaseItem( 0, "test", parent ); ProjectBaseItem* subchild = new ProjectBaseItem( 0, "subtest", child ); model->appendRow( parent ); QCOMPARE( parent->model(), model ); QCOMPARE( child->model(), model ); QCOMPARE( subchild->model(), model ); parent->takeRow( child->row() ); QCOMPARE( child->model(), static_cast(0) ); QCOMPARE( subchild->model(), static_cast(0) ); } void ProjectModelTest::testRename() { QFETCH( int, itemType ); QFETCH( QString, itemText ); QFETCH( QString, newName ); QFETCH( bool, datachangesignal ); QFETCH( QString, expectedItemText ); QFETCH( int, expectedRenameCode ); const KUrl projectFolder = KUrl("file:///dummyprojectfolder"); TestProject* proj = new TestProject; ProjectFolderItem* rootItem = new ProjectFolderItem( proj, projectFolder, 0); proj->setProjectItem( rootItem ); new ProjectFileItem(proj, KUrl("existing"), rootItem); ProjectBaseItem* item = 0; if( itemType == ProjectBaseItem::Target ) { item = new ProjectTargetItem( proj, itemText, rootItem ); } else if( itemType == ProjectBaseItem::File ) { item = new ProjectFileItem( proj, KUrl(projectFolder, itemText), rootItem ); } else if( itemType == ProjectBaseItem::Folder ) { item = new ProjectFolderItem( proj, KUrl(projectFolder, itemText), rootItem ); } else if( itemType == ProjectBaseItem::BuildFolder ) { item = new ProjectBuildFolderItem( proj, KUrl(projectFolder, itemText), rootItem ); } Q_ASSERT( item ); QCOMPARE(item->model(), model); QSignalSpy s( model, SIGNAL(dataChanged(QModelIndex,QModelIndex)) ); ProjectBaseItem::RenameStatus stat = item->rename( newName ); QCOMPARE( (int)stat, expectedRenameCode ); if( datachangesignal ) { QCOMPARE( s.count(), 1 ); QCOMPARE( qvariant_cast( s.takeFirst().at(0) ), item->index() ); } else { QCOMPARE( s.count(), 0 ); } QCOMPARE( item->text(), expectedItemText ); } void ProjectModelTest::testRename_data() { QTest::addColumn( "itemType" ); QTest::addColumn( "itemText" ); QTest::addColumn( "newName" ); QTest::addColumn( "datachangesignal" ); QTest::addColumn( "expectedItemText" ); QTest::addColumn( "expectedRenameCode" ); QTest::newRow("RenameableTarget") << (int)ProjectBaseItem::Target << QString::fromLatin1("target") << QString::fromLatin1("othertarget") << true << QString::fromLatin1("othertarget") << (int)ProjectBaseItem::RenameOk; QTest::newRow("RenameableFile") << (int)ProjectBaseItem::File << QString::fromLatin1("newfile.cpp") << QString::fromLatin1("otherfile.cpp") << true << QString::fromLatin1("otherfile.cpp") << (int)ProjectBaseItem::RenameOk; QTest::newRow("SourceAndDestinationFileEqual") << (int)ProjectBaseItem::File << QString::fromLatin1("newfile.cpp") << QString::fromLatin1("newfile.cpp") << false << QString::fromLatin1("newfile.cpp") << (int)ProjectBaseItem::RenameOk; QTest::newRow("RenameableFolder") << (int)ProjectBaseItem::Folder << QString::fromLatin1("newfolder") << QString::fromLatin1("otherfolder") << true << QString::fromLatin1("otherfolder") << (int)ProjectBaseItem::RenameOk; QTest::newRow("SourceAndDestinationFolderEqual") << (int)ProjectBaseItem::Folder << QString::fromLatin1("newfolder") << QString::fromLatin1("newfolder") << false << QString::fromLatin1("newfolder") << (int)ProjectBaseItem::RenameOk; QTest::newRow("RenameableBuildFolder") << (int)ProjectBaseItem::BuildFolder << QString::fromLatin1("newbfolder") << QString::fromLatin1("otherbfolder") << true << QString::fromLatin1("otherbfolder") << (int)ProjectBaseItem::RenameOk; QTest::newRow("SourceAndDestinationBuildFolderEqual") << (int)ProjectBaseItem::BuildFolder << QString::fromLatin1("newbfolder") << QString::fromLatin1("newbfolder") << false << QString::fromLatin1("newbfolder") << (int)ProjectBaseItem::RenameOk; QTest::newRow("ExistingFileError") << (int)ProjectBaseItem::Folder << QString::fromLatin1("mynew") << QString::fromLatin1("existing") << false << QString::fromLatin1("mynew") << (int)ProjectBaseItem::ExistingItemSameName; QTest::newRow("InvalidNameError") << (int)ProjectBaseItem::File << QString::fromLatin1("mynew") << QString::fromLatin1("other/bash") << false << QString::fromLatin1("mynew") << (int)ProjectBaseItem::InvalidNewName; } void ProjectModelTest::testWithProject() { TestProject* proj = new TestProject(); ProjectFolderItem* rootItem = new ProjectFolderItem( proj, KUrl("file:///dummyprojectfolder"), 0); proj->setProjectItem( rootItem ); ProjectBaseItem* item = model->itemFromIndex( model->index( 0, 0 ) ); QCOMPARE( item, rootItem ); QCOMPARE( item->text(), proj->name() ); QCOMPARE( item->url(), proj->folder() ); } void ProjectModelTest::testAddItemInThread() { ProjectFolderItem* root = new ProjectFolderItem( 0, KUrl("file:///f1"), 0 ); model->appendRow( root ); AddItemThread t( root ); SignalReceiver check( model ); connect( model, SIGNAL(rowsInserted(QModelIndex,int,int)), &check, SLOT(rowsInserted(QModelIndex,int,int)), Qt::DirectConnection ); KDevelop::KDevSignalSpy spy( &t, SIGNAL(addedItems()), Qt::QueuedConnection ); t.start(); QVERIFY(spy.wait( 10000 )); QCOMPARE( qApp->thread(), check.threadOfSignalEmission() ); } void ProjectModelTest::testItemsForUrl() { QFETCH(KUrl, url); QFETCH(ProjectBaseItem*, root); QFETCH(int, matches); model->appendRow(root); QList< ProjectBaseItem* > items = model->itemsForUrl(url); QCOMPARE(items.size(), matches); foreach(ProjectBaseItem* item, items) { QVERIFY(item->url().equals(url, KUrl::CompareWithoutTrailingSlash)); } model->clear(); } void ProjectModelTest::testItemsForUrl_data() { QTest::addColumn("url"); QTest::addColumn("root"); QTest::addColumn("matches"); { ProjectFolderItem* root = new ProjectFolderItem(0, KUrl("file:///tmp/")); ProjectFileItem* file = new ProjectFileItem(0, KUrl(root->url(), "a"), root); QTest::newRow("find one") << file->url() << static_cast(root) << 1; } { ProjectFolderItem* root = new ProjectFolderItem(0, KUrl("file:///tmp/")); ProjectFolderItem* folder = new ProjectFolderItem(0, KUrl(root->url(), "a"), root); ProjectFileItem* file = new ProjectFileItem(0, KUrl(folder->url(), "foo"), folder); ProjectTargetItem* target = new ProjectTargetItem(0, "b", root); ProjectFileItem* file2 = new ProjectFileItem(0, file->url(), target); Q_UNUSED(file2); QTest::newRow("find two") << file->url() << static_cast(root) << 2; } } void ProjectModelTest::testProjectProxyModel() { ProjectFolderItem* root = new ProjectFolderItem(0, KUrl("file:///tmp/")); new ProjectFileItem(0, KUrl("file:///tmp/a1/"), root); new ProjectFileItem(0, KUrl("file:///tmp/b1/"), root); new ProjectFileItem(0, KUrl("file:///tmp/c1/"), root); new ProjectFileItem(0, KUrl("file:///tmp/d1/"), root); model->appendRow(root); QModelIndex proxyRoot = proxy->mapFromSource(root->index()); QCOMPARE(model->rowCount(root->index()), 4); QCOMPARE(proxy->rowCount(proxyRoot), 4); proxy->setFilterString("*1"); QCOMPARE(proxy->rowCount(proxyRoot), 4); proxy->setFilterString("a*"); debugItemModel(proxy); QCOMPARE(proxy->rowCount(proxyRoot), 1); model->clear(); } void ProjectModelTest::testProjectFileSet() { TestProject* project = new TestProject; QVERIFY(project->fileSet().isEmpty()); KUrl url("file:///tmp/a"); ProjectFileItem* item = new ProjectFileItem(project, url, project->projectItem()); QCOMPARE(project->fileSet().size(), 1); qDebug() << url << project->fileSet().begin()->toUrl(); QCOMPARE(project->fileSet().begin()->toUrl(), url); delete item; QVERIFY(project->fileSet().isEmpty()); } void ProjectModelTest::testProjectFileIcon() { ProjectFileItem* item = new ProjectFileItem(0, KUrl("/tmp/foo.txt")); const QString txtIcon = KMimeType::iconNameForUrl(item->url()); QCOMPARE(item->iconName(), txtIcon); item->setUrl(KUrl("/tmp/bar.cpp")); QCOMPARE(item->iconName(), KMimeType::iconNameForUrl(item->url())); QVERIFY(item->iconName() != txtIcon); } QTEST_KDEMAIN( ProjectModelTest, GUI) #include "projectmodeltest.moc" #include "moc_projectmodeltest.cpp" diff --git a/util/dbus_socket_transformer/main.cpp b/util/dbus_socket_transformer/main.cpp index 5168e943b2..2e951a1e42 100644 --- a/util/dbus_socket_transformer/main.cpp +++ b/util/dbus_socket_transformer/main.cpp @@ -1,417 +1,416 @@ /*************************************************************************** * Copyright 2011 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 Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include #include #include #include #include -#include #include #include #include #include #include #include #include #include #ifndef HAVE_MSG_NOSIGNAL #define MSG_NOSIGNAL 0 #endif /** * The goal of this utility is transforming the abstract unix-socket which is used by dbus * into a TCP socket which can be forwarded to a target machine by ssh tunneling, and then on * the target machine back into an abstract unix socket. * * This tool basically works similar to the "socat" utility, except that it works properly * for this special case. It is merely responsible for the transformation between abstract unix * sockets and tcp sockets. * * Furthermore, this tool makes the 'EXTERNAL' dbus authentication mechanism work even across * machines with different user IDs. * * This is how the EXTERNAL mechanism works (I found this in a comment of some ruby dbus library): * Take the user id (eg integer 1000) make a string out of it "1000", take * each character and determin hex value "1" => 0x31, "0" => 0x30. You * obtain for "1000" => 31303030 This is what the server is expecting. * Why? I dunno. How did I come to that conclusion? by looking at rbus * code. I have no idea how he found that out. * * The dbus client performs the EXTERNAL authentication by sending "AUTH EXTERNAL 31303030\r\n" once * after opening the connection, so we can "repair" the authentication by overwriting the token in that * string through the correct one. * */ const bool debug = false; /** * Returns the valid dbus EXTERNAL authentication token for the current user (see above) * */ std::string getAuthToken() { // Get uid int uid = getuid(); std::ostringstream uidStream; uidStream << uid; std::string uidStr = uidStream.str(); const char hexdigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; std::ostringstream hexStream; for(uint i = 0; i < uidStr.size(); ++i) { unsigned char byte = (unsigned char)uidStr[i]; hexStream << hexdigits[byte >> 4] << hexdigits[byte & 0x0f]; } return hexStream.str(); } /** * Shuffles all data between the two file-descriptors until one of them fails or reaches EOF. * */ void shuffleBetweenStreams(int side1, int side2, bool fixSide1AuthToken) { char buffer[1000]; char buffer2[1000]; // Set non-blocking mode int opts = fcntl(side1,F_GETFL); opts |= O_NONBLOCK; fcntl(side1, F_SETFL, opts); opts = fcntl(side2,F_GETFL); opts |= O_NONBLOCK; fcntl(side2, F_SETFL, opts); while(true) { int r1 = read(side1, buffer, 500); // We read less than 1000, so we have same additional space when changing the auth token int r2 = read(side2, buffer2, 500); if(r1 < -1 || r1 == 0) { if(debug) std::cerr << "stream 1 failed: " << r1 << std::endl; return; } if(r2 < -1 || r2 == 0) { if(debug) std::cerr << "stream 2 failed: " << r2 << std::endl; return; } if(r1 > 0) { if(debug) std::cerr << "transferring " << r1 << " from 1 to 2" << std::endl; if(fixSide1AuthToken) { if(r1 > 15 && memcmp(buffer, "\0AUTH EXTERNAL ", 15) == 0) { int endPos = -1; for(int i = 15; i < r1; ++i) { if(buffer[i] == '\r') { endPos = i; break; } } if(endPos != -1) { std::string oldToken = std::string(buffer + 15, endPos - 15); std::string newToken = getAuthToken(); int difference = newToken.size() - oldToken.size(); r1 += difference; assert(r1 > 0 && r1 <= 1000); memmove(buffer + endPos + difference, buffer + endPos, r1 - difference - endPos); memcpy(buffer + 15, newToken.data(), newToken.size()); assert(buffer[endPos + difference] == '\r'); assert(buffer[endPos + difference - 1] == newToken[newToken.size()-1]); }else{ std::cout << "could not fix auth token, not enough data available" << std::endl; } }else{ std::cout << "could not fix auth token" << std::endl; } fixSide1AuthToken = false; } opts = fcntl(side2,F_GETFL); opts ^= O_NONBLOCK; fcntl(side2, F_SETFL, opts); int w2 = send(side2, buffer, r1, MSG_NOSIGNAL); if(w2 < 0) { if(debug) std::cerr << "writing to side 2 failed, ending: " << w2 << std::endl; return; } assert(w2 == r1); opts = fcntl(side2,F_GETFL); opts |= O_NONBLOCK; fcntl(side2, F_SETFL, opts); } if(r2 > 0) { if(debug) std::cerr << "transferring " << r2 << " from 2 to 1" << std::endl; opts = fcntl(side1,F_GETFL); opts ^= O_NONBLOCK; fcntl(side1, F_SETFL, opts); int w1 = send(side1, buffer2, r2, MSG_NOSIGNAL); if(w1 < 0) { if(debug) std::cerr << "writing to side 1 failed, ending: " << w1 << std::endl; return; } assert(w1 == r2); opts = fcntl(side1,F_GETFL); opts |= O_NONBLOCK; fcntl(side1, F_SETFL, opts); } usleep(1000); } } int main(int argc, char** argv) { int serverfd; if(argc < 2) { std::cerr << "need arguments:" << std::endl; std::cerr << "[port] - Open a server on this TCP port and forward connections to the local DBUS session" " (the DBUS_SESSION_BUS_ADDRESS environment variable must be set)"; std::cerr << "[port] [fake dbus path] - Open a server on the fake dbus path and forward connections to the given local TCP port"; std::cerr << "" "The last argument may be the --bind-only option, in which case the application only tries to" "open the server, but does not wait for clients to connect. This is useful to test whether the" "server port/path is available."; return 10; } bool waitForClients = true; if(std::string(argv[argc-1]) == "--bind-only") { waitForClients = false; argc -= 1; } std::string dbusAddress(getenv("DBUS_SESSION_BUS_ADDRESS")); std::string path; if(argc == 2) { if(waitForClients && debug) std::cout << "forwarding from the local TCP port " << argv[1] << " to the local DBUS session at " << dbusAddress.data() << std::endl; if(dbusAddress.empty()) { std::cerr << "The DBUS_SESSION_BUS_ADDRESS environment variable is not set" << std::endl; return 1; } // Open a TCP server std::string abstractPrefix("unix:abstract="); if(dbusAddress.substr(0, abstractPrefix.size()) != abstractPrefix) { std::cerr << "DBUS_SESSION_BUS_ADDRESS does not seem to use an abstract unix domain socket as expected" << std::endl; return 2; } path = dbusAddress.substr(abstractPrefix.size(), dbusAddress.size() - abstractPrefix.size()); if(path.find(",guid=") != std::string::npos) path = path.substr(0, path.find(",guid=")); // Mark it as an abstract unix domain socket path = path; serverfd = socket(AF_INET, SOCK_STREAM, 0); if (serverfd < 0) { if(waitForClients) std::cerr << "ERROR opening server socket" << std::endl; return 3; } int portno = atoi(argv[1]); sockaddr_in server_addr; bzero((char *) &server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(portno); if (bind(serverfd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) { if(waitForClients) std::cerr << "ERROR opening the server" << std::endl; return 7; } }else if(argc == 3) { if(waitForClients && debug) std::cout << "forwarding from the local abstract unix domain socket " << argv[2] << " to the local TCP port " << argv[1] << std::endl; // Open a unix domain socket server serverfd = socket(AF_UNIX, SOCK_STREAM, 0); if (serverfd < 0) { if(waitForClients) std::cerr << "ERROR opening server socket" << std::endl; return 3; } path = std::string(argv[2]); sockaddr_un serv_addr; bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sun_family = AF_UNIX; serv_addr.sun_path[0] = '\0'; // Mark as an abstract socket strcpy(serv_addr.sun_path+1, path.data()); if(debug) std::cout << "opening at " << path.data() << std::endl; if (bind(serverfd,(sockaddr *) &serv_addr, sizeof (serv_addr.sun_family) + 1 + path.length()) < 0) { if(waitForClients) std::cerr << "ERROR opening the server" << std::endl; return 7; } }else{ std::cerr << "Wrong arguments"; return 1; } listen(serverfd, 10); while(waitForClients) { if(debug) std::cerr << "waiting for client" << std::endl; sockaddr_in cli_addr; socklen_t clilen = sizeof(cli_addr); int connectedclientsockfd = accept(serverfd, (struct sockaddr *) &cli_addr, &clilen); if(connectedclientsockfd < 0) { std::cerr << "ERROR on accept" << std::endl; return 8; } if(debug) std::cerr << "got client" << std::endl; int sockfd; int addrSize; sockaddr* useAddr = 0; sockaddr_un serv_addru; sockaddr_in serv_addrin; if(argc == 2) { sockfd = socket(AF_UNIX, SOCK_STREAM, 0); if (sockfd < 0) { std::cerr << "ERROR opening socket" << std::endl; return 3; } bzero((char *) &serv_addru, sizeof(serv_addru)); serv_addru.sun_family = AF_UNIX; serv_addru.sun_path[0] = '\0'; // Mark as an abstract socket strcpy(serv_addru.sun_path+1, path.data()); addrSize = sizeof (serv_addru.sun_family) + 1 + path.size(); useAddr = (sockaddr*)&serv_addru; if(debug) std::cout << "connecting to " << path.data() << std::endl; }else{ sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { std::cerr << "ERROR opening socket" << std::endl; return 3; } int port = atoi(argv[1]); hostent *server = gethostbyname("localhost"); if(server == NULL) { std::cerr << "failed to get server" << std::endl; return 5; } bzero((char *) &serv_addrin, sizeof(serv_addrin)); serv_addrin.sin_family = AF_INET; serv_addrin.sin_addr.s_addr = INADDR_ANY; serv_addrin.sin_port = htons(port); bcopy((char *)server->h_addr, (char *)&serv_addrin.sin_addr.s_addr, server->h_length); addrSize = sizeof (serv_addrin); useAddr = (sockaddr*)&serv_addrin; if(debug) std::cout << "connecting to port " << port << std::endl; } if (connect(sockfd, useAddr, addrSize) < 0) { int res = errno; if(res == ECONNREFUSED) std::cerr << "ERROR while connecting: connection refused" << std::endl; else if(res == ENOENT) std::cerr << "ERROR while connecting: no such file or directory" << std::endl; else std::cerr << "ERROR while connecting" << std::endl; return 5; } shuffleBetweenStreams(connectedclientsockfd, sockfd, argc == 2); close(sockfd); close(connectedclientsockfd); } return 0; } diff --git a/vcs/vcspluginhelper.cpp b/vcs/vcspluginhelper.cpp index 0cd6a6ac79..1c02fff952 100644 --- a/vcs/vcspluginhelper.cpp +++ b/vcs/vcspluginhelper.cpp @@ -1,501 +1,500 @@ /*************************************************************************** * Copyright 2008 Andreas Pakulat * * Copyright 2010 Aleix Pol Gonzalez * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "vcspluginhelper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "interfaces/idistributedversioncontrol.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_KOMPARE #include #endif #include #include #include #include "vcsstatusinfo.h" #include #include -#include #include "widgets/vcsdiffpatchsources.h" #include "widgets/flexibleaction.h" #include #include "vcsevent.h" #include #include #include #include #include namespace KDevelop { struct VcsPluginHelper::VcsPluginHelperPrivate { IPlugin * plugin; IBasicVersionControl * vcs; KUrl::List ctxUrls; KAction * commitAction; KAction * addAction; KAction * updateAction; KAction * historyAction; KAction * annotationAction; KAction * diffToBaseAction; KAction * revertAction; KAction * diffForRevAction; KAction * diffForRevGlobalAction; KAction * pushAction; KAction * pullAction; QPointer modificationTimer; void createActions(VcsPluginHelper* parent) { commitAction = new KAction(KIcon("svn-commit"), i18n("Commit..."), parent); updateAction = new KAction(KIcon("svn-update"), i18n("Update"), parent); addAction = new KAction(KIcon("list-add"), i18n("Add"), parent); diffToBaseAction = new KAction(KIcon("vcs_diff"), i18n("Show Differences..."), parent); revertAction = new KAction(KIcon("archive-remove"), i18n("Revert"), parent); historyAction = new KAction(KIcon("view-history"), i18n("History..."), parent); annotationAction = new KAction(KIcon("user-properties"), i18n("Annotation..."), parent); diffForRevAction = new KAction(KIcon("vcs_diff"), i18n("Show Diff..."), parent); diffForRevGlobalAction = new KAction(KIcon("vcs_diff"), i18n("Show Diff (all files)..."), parent); pushAction = new KAction(KIcon("arrow-up-double"), i18n("Push"), parent); pullAction = new KAction(KIcon("arrow-down-double"), i18n("Pull"), parent); connect(commitAction, SIGNAL(triggered()), parent, SLOT(commit())); connect(addAction, SIGNAL(triggered()), parent, SLOT(add())); connect(updateAction, SIGNAL(triggered()), parent, SLOT(update())); connect(diffToBaseAction, SIGNAL(triggered()), parent, SLOT(diffToBase())); connect(revertAction, SIGNAL(triggered()), parent, SLOT(revert())); connect(historyAction, SIGNAL(triggered()), parent, SLOT(history())); connect(annotationAction, SIGNAL(triggered()), parent, SLOT(annotation())); connect(diffForRevAction, SIGNAL(triggered()), parent, SLOT(diffForRev())); connect(diffForRevGlobalAction, SIGNAL(triggered()), parent, SLOT(diffForRevGlobal())); connect(pullAction, SIGNAL(triggered()), parent, SLOT(pull())); connect(pushAction, SIGNAL(triggered()), parent, SLOT(push())); } bool allLocalFiles(const KUrl::List& urls) { bool ret=true; foreach(const KUrl& url, urls) { QFileInfo info(url.toLocalFile()); ret &= info.isFile(); } return ret; } QMenu* createMenu() { bool allVersioned=true; foreach(const KUrl& url, ctxUrls) { allVersioned=allVersioned && vcs->isVersionControlled(url); if(!allVersioned) break; } QMenu* menu=new QMenu(vcs->name()); menu->setIcon(KIcon(ICore::self()->pluginController()->pluginInfo(plugin).icon())); menu->addAction(commitAction); if(plugin->extension()) { menu->addAction(pushAction); menu->addAction(pullAction); } else { menu->addAction(updateAction); } menu->addSeparator(); menu->addAction(addAction); menu->addAction(revertAction); menu->addSeparator(); menu->addAction(historyAction); menu->addAction(annotationAction); menu->addAction(diffToBaseAction); const bool singleVersionedFile = ctxUrls.count() == 1 && allVersioned; historyAction->setEnabled(singleVersionedFile); annotationAction->setEnabled(singleVersionedFile && allLocalFiles(ctxUrls)); diffToBaseAction->setEnabled(singleVersionedFile); commitAction->setEnabled(singleVersionedFile); return menu; } }; VcsPluginHelper::VcsPluginHelper(KDevelop::IPlugin* parent, KDevelop::IBasicVersionControl* vcs) : QObject(parent) , d(new VcsPluginHelperPrivate()) { Q_ASSERT(vcs); Q_ASSERT(parent); d->plugin = parent; d->vcs = vcs; d->createActions(this); } VcsPluginHelper::~VcsPluginHelper() {} void VcsPluginHelper::setupFromContext(Context* context) { d->ctxUrls.clear(); { KDevelop::ProjectItemContext* prjctx = dynamic_cast(context); if (prjctx) { foreach(KDevelop::ProjectBaseItem* item, prjctx->items()) { if(!item->target()) d->ctxUrls.append(item->url()); } } } { KDevelop::EditorContext* editctx = dynamic_cast(context); if (editctx) { d->ctxUrls.append(editctx->url()); } } { KDevelop::FileContext* filectx = dynamic_cast(context); if (filectx) { d->ctxUrls = filectx->urls(); } } } KUrl::List const & VcsPluginHelper::contextUrlList() { return d->ctxUrls; } QMenu* VcsPluginHelper::commonActions() { /* TODO: the following logic to determine which actions need to be enabled * or disabled does not work properly. What needs to be implemented is that * project items that are vc-controlled enable all except add, project * items that are not vc-controlled enable add action. For urls that cannot * be made into a project item, or if the project has no associated VC * plugin we need to check whether a VC controls the parent dir, if we have * one we assume the urls can be added but are not currently controlled. If * the url is already version controlled then just enable all except add */ return d->createMenu(); } #define EXECUTE_VCS_METHOD( method ) \ d->plugin->core()->runController()->registerJob( d->vcs-> method ( d->ctxUrls ) ) #define SINGLEURL_SETUP_VARS \ KDevelop::IBasicVersionControl* iface = d->vcs;\ const KUrl & url = d->ctxUrls.front(); void VcsPluginHelper::revert() { VcsJob* job=d->vcs->revert(d->ctxUrls); connect(job, SIGNAL(finished(KJob*)), SLOT(revertDone(KJob*))); foreach(const KUrl& url, d->ctxUrls) { IDocument* doc=ICore::self()->documentController()->documentForUrl(url); if(doc && doc->textDocument()) { KTextEditor::ModificationInterface* modif = dynamic_cast(doc->textDocument()); if (modif) { modif->setModifiedOnDiskWarning(false); } doc->textDocument()->setModified(false); } } job->setProperty("urls", d->ctxUrls); d->plugin->core()->runController()->registerJob(job); } void VcsPluginHelper::revertDone(KJob* job) { d->modificationTimer = new QTimer; d->modificationTimer->setInterval(100); connect(d->modificationTimer, SIGNAL(timeout()), SLOT(delayedModificationWarningOn())); d->modificationTimer->setProperty("urls", job->property("urls")); d->modificationTimer->start(); } void VcsPluginHelper::delayedModificationWarningOn() { KUrl::List urls = d->modificationTimer->property("urls").value(); foreach(const KUrl& url, urls) { IDocument* doc=ICore::self()->documentController()->documentForUrl(url); if(doc) { doc->reload(); KTextEditor::ModificationInterface* modif=dynamic_cast(doc->textDocument()); modif->setModifiedOnDiskWarning(true); } } d->modificationTimer->deleteLater(); } void VcsPluginHelper::diffJobFinished(KJob* job) { KDevelop::VcsJob* vcsjob = qobject_cast(job); Q_ASSERT(vcsjob); if (vcsjob->status() == KDevelop::VcsJob::JobSucceeded) { KDevelop::VcsDiff d = vcsjob->fetchResults().value(); if(d.isEmpty()) KMessageBox::information(ICore::self()->uiController()->activeMainWindow(), i18n("There are no differences."), i18n("VCS support")); else { VCSDiffPatchSource* patch=new VCSDiffPatchSource(d); showVcsDiff(patch); } } else { KMessageBox::error(ICore::self()->uiController()->activeMainWindow(), vcsjob->errorString(), i18n("Unable to get difference.")); } } void VcsPluginHelper::diffToBase() { SINGLEURL_SETUP_VARS ICore::self()->documentController()->saveAllDocuments(); VCSDiffPatchSource* patch =new VCSDiffPatchSource(new VCSStandardDiffUpdater(iface, url)); showVcsDiff(patch); } void VcsPluginHelper::diffForRev() { QAction* action = qobject_cast( sender() ); Q_ASSERT(action); Q_ASSERT(action->data().canConvert()); VcsRevision rev = action->data().value(); SINGLEURL_SETUP_VARS ICore::self()->documentController()->saveAllDocuments(); VcsRevision prev = KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Previous); KDevelop::VcsJob* job = iface->diff(url, prev, rev ); connect(job, SIGNAL(finished(KJob*)), this, SLOT(diffJobFinished(KJob*))); d->plugin->core()->runController()->registerJob(job); } void VcsPluginHelper::diffForRevGlobal() { for(int a = 0; a < d->ctxUrls.size(); ++a) { KUrl& url(d->ctxUrls[a]); IProject* project = ICore::self()->projectController()->findProjectForUrl( url ); if( project ) url = project->folder(); } diffForRev(); } void VcsPluginHelper::history(const VcsRevision& rev) { SINGLEURL_SETUP_VARS KDialog* dlg = new KDialog(ICore::self()->uiController()->activeMainWindow()); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setButtons(KDialog::Close); dlg->setCaption(i18nc("%1: path or URL, %2: name of a version control system", "%2 History (%1)", url.pathOrUrl(), iface->name())); KDevelop::VcsEventWidget* logWidget = new KDevelop::VcsEventWidget(url, rev, iface, dlg); dlg->setMainWidget(logWidget); dlg->show(); } void VcsPluginHelper::annotation() { SINGLEURL_SETUP_VARS KDevelop::IDocument* doc = ICore::self()->documentController()->documentForUrl(url); if (!doc) doc = ICore::self()->documentController()->openDocument(url); if (doc && doc->textDocument()) { KDevelop::VcsJob* job = iface->annotate(url); if( !job ) { kWarning() << "Couldn't create annotate job for:" << url << "with iface:" << iface << dynamic_cast( iface ); return; } KTextEditor::AnnotationInterface* annotateiface = qobject_cast(doc->textDocument()); KTextEditor::AnnotationViewInterface* viewiface = qobject_cast(doc->textDocument()->activeView()); if (annotateiface && viewiface) { KDevelop::VcsAnnotationModel* model = new KDevelop::VcsAnnotationModel(job, url, doc->textDocument()); annotateiface->setAnnotationModel(model); viewiface->setAnnotationBorderVisible(true); connect(doc->textDocument()->activeView(), SIGNAL(annotationContextMenuAboutToShow(KTextEditor::View*,QMenu*,int)), this, SLOT(annotationContextMenuAboutToShow(KTextEditor::View*,QMenu*,int))); } else { KMessageBox::error(0, i18n("Cannot display annotations, missing interface KTextEditor::AnnotationInterface for the editor.")); delete job; } } else { KMessageBox::error(0, i18n("Cannot execute annotate action because the " "document was not found, or was not a text document:\n%1", url.pathOrUrl())); } } class CopyFunction : public AbstractFunction { public: CopyFunction(const QString& tocopy) : m_tocopy(tocopy) {} void operator()() { QApplication::clipboard()->setText(m_tocopy); } private: QString m_tocopy; }; class HistoryFunction : public AbstractFunction { public: HistoryFunction(VcsPluginHelper* helper, const VcsRevision& rev) : m_helper(helper), m_rev(rev) {} void operator()() { m_helper->history(m_rev); } private: VcsPluginHelper* m_helper; VcsRevision m_rev; }; void VcsPluginHelper::annotationContextMenuAboutToShow( KTextEditor::View* view, QMenu* menu, int line ) { KTextEditor::AnnotationInterface* annotateiface = qobject_cast(view->document()); VcsAnnotationModel* model = qobject_cast( annotateiface->annotationModel() ); Q_ASSERT(model); VcsRevision rev = model->revisionForLine(line); // check if the user clicked on a row without revision information if (rev.revisionType() == VcsRevision::Invalid) { // in this case, do not action depending on revision informations return; } d->diffForRevAction->setData(QVariant::fromValue(rev)); d->diffForRevGlobalAction->setData(QVariant::fromValue(rev)); menu->addSeparator(); menu->addAction(d->diffForRevAction); menu->addAction(d->diffForRevGlobalAction); menu->addAction(new FlexibleAction(KIcon("edit-copy"), i18n("Copy Revision"), new CopyFunction(rev.revisionValue().toString()), menu)); menu->addAction(new FlexibleAction(KIcon("view-history"), i18n("History..."), new HistoryFunction(this, rev), menu)); } void VcsPluginHelper::update() { EXECUTE_VCS_METHOD(update); } void VcsPluginHelper::add() { EXECUTE_VCS_METHOD(add); } void VcsPluginHelper::commit() { Q_ASSERT(!d->ctxUrls.isEmpty()); ICore::self()->documentController()->saveAllDocuments(); KUrl url = d->ctxUrls.first(); // We start the commit UI no matter whether there is real differences, as it can also be used to commit untracked files VCSCommitDiffPatchSource* patchSource = new VCSCommitDiffPatchSource(new VCSStandardDiffUpdater(d->vcs, url)); bool ret = showVcsDiff(patchSource); if(!ret) { VcsCommitDialog *commitDialog = new VcsCommitDialog(patchSource); commitDialog->setCommitCandidates(patchSource->infos()); commitDialog->exec(); } } void VcsPluginHelper::push() { foreach(const KUrl& url, d->ctxUrls) { VcsJob* job = d->plugin->extension()->push(url, VcsLocation()); ICore::self()->runController()->registerJob(job); } } void VcsPluginHelper::pull() { foreach(const KUrl& url, d->ctxUrls) { VcsJob* job = d->plugin->extension()->pull(VcsLocation(), url); ICore::self()->runController()->registerJob(job); } } } #include "vcspluginhelper.moc"