diff --git a/plugins/outlineview/outlinenode.cpp b/plugins/outlineview/outlinenode.cpp index 96340b9c6b..76e4777088 100644 --- a/plugins/outlineview/outlinenode.cpp +++ b/plugins/outlineview/outlinenode.cpp @@ -1,272 +1,297 @@ /* * KDevelop outline view * Copyright 2010, 2015 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "outlinenode.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "debug_outline.h" using namespace KDevelop; OutlineNode::OutlineNode(const QString& text, OutlineNode* parent) : m_cachedText(text) , m_parent(parent) { } OutlineNode::OutlineNode(DUContext* ctx, const QString& name, OutlineNode* parent) : m_cachedText(name) , m_declOrContext(ctx) , m_parent(parent) { KTextEditor::CodeCompletionModel::CompletionProperties prop; + qCDebug(PLUGIN_OUTLINE, "ctx->type=%d", ctx->type()); switch (ctx->type()) { case KDevelop::DUContext::Class: prop |= KTextEditor::CodeCompletionModel::Class; break; case KDevelop::DUContext::Enum: prop |= KTextEditor::CodeCompletionModel::Enum; break; case KDevelop::DUContext::Function: prop |= KTextEditor::CodeCompletionModel::Function; break; case KDevelop::DUContext::Namespace: prop |= KTextEditor::CodeCompletionModel::Namespace; break; case KDevelop::DUContext::Template: prop |= KTextEditor::CodeCompletionModel::Template; break; default: break; } m_cachedIcon = DUChainUtils::iconForProperties(prop); appendContext(ctx, ctx->topContext()); } OutlineNode::OutlineNode(Declaration* decl, OutlineNode* parent) : m_declOrContext(decl) , m_parent(parent) { // qCDebug(PLUGIN_OUTLINE) << "Adding:" << decl->qualifiedIdentifier().toString() << ": " <identifier().toString(); m_cachedIcon = DUChainUtils::iconForDeclaration(decl); if (NamespaceAliasDeclaration* alias = dynamic_cast(decl)) { //e.g. C++ using namespace statement m_cachedText = alias->importIdentifier().toString(); } else if (ClassMemberDeclaration* member = dynamic_cast(decl)) { if (member->isFriend()) { m_cachedText = "friend " + m_cachedText; } } if (AbstractType::Ptr type = decl->abstractType()) { //add the (function return) type at the end (after a colon - like UML) //so that the first thing seen is the name of the function/variable //and not the (function return) type AbstractType::WhichType typeEnum = type->whichType(); switch (typeEnum) { case AbstractType::TypeFunction: { FunctionType::Ptr func = type.cast(); // func->partToString() does not add the argument names -> do it manually if (DUContext* fCtx = DUChainUtils::getFunctionContext(decl)) { m_cachedText += '('; bool first = true; foreach (Declaration* childDecl, fCtx->localDeclarations(decl->topContext())) { if (first) { first = false; } else { m_cachedText += QStringLiteral(", "); } m_cachedText += childDecl->abstractType()->toString(); auto ident = childDecl->identifier(); if (!ident.isEmpty()) { m_cachedText += ' ' + ident.toString(); } } m_cachedText += ')'; } else { qCWarning(PLUGIN_OUTLINE) << "Missing function context:" << decl->qualifiedIdentifier().toString(); m_cachedText += func->partToString(FunctionType::SignatureArguments); } //constructors/destructors have no return type, a trailing semicolon would look stupid if (func->returnType()) { m_cachedText += " : " + func->partToString(FunctionType::SignatureReturn); } return; // don't append any children here! } case AbstractType::TypeEnumeration: //no need to append the fully qualified type break; case AbstractType::TypeEnumerator: //no need to append the fully qualified type Q_ASSERT(decl->type()); m_cachedText += " = " + decl->type()->valueAsString(); break; case AbstractType::TypeStructure: { //this seems to be the way it has to be done (after grepping through source code) //TODO shouldn't there be some kind of isFriend() functionality? static IndexedIdentifier friendIdentifier(Identifier("friend")); const bool isFriend = decl->indexedIdentifier() == friendIdentifier; if (isFriend) { //FIXME There seems to be no way of finding out whether the friend is class/struct/etc m_cachedText += ' ' + type->toString(); } break; } case AbstractType::TypeAlias: { //append the type it aliases TypeAliasType::Ptr alias = type.cast(); if (AbstractType::Ptr targetType = alias->type()) { m_cachedText += " : " + targetType->toString(); } } break; default: QString typeStr = type->toString(); if (!typeStr.isEmpty()) { m_cachedText += " : " + typeStr; } } } //these two don't seem to be hit if (decl->isAutoDeclaration()) { m_cachedText = "Implicit: " + m_cachedText; } if (decl->isAnonymous()) { m_cachedText = "" + m_cachedText; } if (DUContext* ctx = decl->internalContext()) { appendContext(ctx, decl->topContext()); } if (m_cachedText.isEmpty()) { m_cachedText = i18nc("An anonymous declaration (class, function, etc.)", ""); } } std::unique_ptr OutlineNode::dummyNode() { return std::unique_ptr(new OutlineNode(QStringLiteral(""), nullptr)); } std::unique_ptr OutlineNode::fromTopContext(TopDUContext* ctx) { auto result = dummyNode(); result->appendContext(ctx, ctx); return result; } void OutlineNode::appendContext(DUContext* ctx, TopDUContext* top) { // qDebug() << ctx->scopeIdentifier().toString() << "context type=" << ctx->type(); foreach (Declaration* childDecl, ctx->localDeclarations(top)) { if (childDecl) { m_children.emplace_back(childDecl, this); } } bool certainlyRequiresSorting = false; foreach (DUContext* childContext, ctx->childContexts()) { if (childContext->owner()) { // if there is a onwner, this will already have been handled by the loop above // TODO: is this always true? With my testing so far it seems to be // qDebug() << childContext->scopeIdentifier(true).toString() // << " has an owner declaration: " << childContext->owner()->toString() << "-> skip"; continue; } QVector decls = childContext->localDeclarations(top); if (decls.isEmpty()) { continue; } // we now know that we will have o sort since we appended a node in the wrong order certainlyRequiresSorting = true; QString ctxName = childContext->scopeIdentifier(true).toString(); // if child context is a template context or if name is empty append to current list, // otherwise create a new context node if (childContext->type() == DUContext::Template || ctxName.isEmpty()) { //append all subcontexts to this node appendContext(childContext, top); } else { // context without matching declaration, for example the definition of // "class Foo::Bar if it was forward declared in a namespace before: // namespace Foo { class Bar; } // class Foo::Bar { ... }; // TODO: icon and location for the namespace - m_children.emplace_back(childContext, ctxName, this); + if (childContext->type() == DUContext::ContextType::Helper) { + // This context could be for a definition of an existing class method. + // If we don't merge all those context end up with a tree like this: + // +-+- FooClass + // | \-- method1() + // +-+- FooClass + // | \-- method2() + // \ OtherStuff + auto it = std::find_if(m_children.begin(), m_children.end(), [childContext](const OutlineNode& node) { + if (DUContext* ctx = dynamic_cast(node.duChainObject())) { + return ctx->equalScopeIdentifier(childContext); + } + return false; + }); + if (it != m_children.end()) { + it->appendContext(childContext, top); + } + else { + // TODO: get the correct icon for the context + m_children.emplace_back(childContext, ctxName, this); + } + } else { + // just add the context + m_children.emplace_back(childContext, ctxName, this); + } } } // we now need to sort since sometimes the elements from ctx->localDeclarations(top) // are not in the order they appear in the source. Additionally, if we had any child // contexts that were added, they will be at the end of the list // and need to be moved to the correct location. In that case certainlyRequiresSorting // will be true and we can pass it to sortByLocation() to skip the std::is_sorted() call sortByLocation(certainlyRequiresSorting); } void OutlineNode::sortByLocation(bool requiresSorting) { if (m_children.size() <= 1) { return; } // TODO: does it make sense to cache m_declOrContext->range().start? // adds 8 bytes to each node, but save a lot of pointer lookups when sorting // qDebug("sorting children of %s (%p) by location", qPrintable(m_cachedText), this); auto compare = [](const OutlineNode& n1, const OutlineNode& n2) -> bool { // nodes without decl always go at the end if (!n1.m_declOrContext) { return false; } else if (!n2.m_declOrContext) { return true; } return n1.m_declOrContext->range().start < n2.m_declOrContext->range().start; }; // since most nodes will be correctly sorted we check that before calling std::sort(). // This saves a lot of move ctor/assingnment calls in the common case. // If we appended a context without a Declaration* we know that it will be unsorted // so we can pass requiresSorting = true to skip the useless std::is_sorted() call. // uncomment the following qDebug() lines to see whether this optimization really makes sense if (requiresSorting || !std::is_sorted(m_children.begin(), m_children.end(), compare)) { // qDebug("Need to sort node %s(%p)", qPrintable(m_cachedText), this); std::sort(m_children.begin(), m_children.end(), compare); } else { // qDebug("Node %s(%p) was sorted!", qPrintable(m_cachedText), this); } } OutlineNode::~OutlineNode() { } diff --git a/plugins/outlineview/outlinenode.h b/plugins/outlineview/outlinenode.h index 64e9327d79..fef935ac7d 100644 --- a/plugins/outlineview/outlinenode.h +++ b/plugins/outlineview/outlinenode.h @@ -1,161 +1,161 @@ /* * KDevelop outline view * Copyright 2010, 2015 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once #include #include #include #include #include #include #include namespace KDevelop { class Declaration; class DUContext; } class OutlineNode { Q_DISABLE_COPY(OutlineNode) void appendContext(KDevelop::DUContext* ctx, KDevelop::TopDUContext* top); void sortByLocation(bool requiresSorting); public: OutlineNode(const QString& text, OutlineNode* parent); OutlineNode(OutlineNode&& other) noexcept; OutlineNode& operator=(OutlineNode&& other) noexcept; OutlineNode(KDevelop::Declaration* decl, OutlineNode* parent); OutlineNode(KDevelop::DUContext* ctx, const QString& name, OutlineNode* parent); virtual ~OutlineNode(); QIcon icon() const; QString text() const; const OutlineNode* parent() const; const std::vector& children() const; int childCount() const; const OutlineNode* childAt(int index) const; int indexOf(const OutlineNode* child) const; static std::unique_ptr fromTopContext(KDevelop::TopDUContext* ctx); static std::unique_ptr dummyNode(); - KDevelop::DUChainBase* duChainObject(); + KDevelop::DUChainBase* duChainObject() const; friend void swap(OutlineNode& n1, OutlineNode& n2); private: QString m_cachedText; QIcon m_cachedIcon; KDevelop::DUChainBasePointer m_declOrContext; OutlineNode* m_parent; std::vector m_children; }; inline int OutlineNode::childCount() const { return m_children.size(); } inline const std::vector& OutlineNode::children() const { return m_children; } inline const OutlineNode* OutlineNode::childAt(int index) const { return &m_children.at(index); } inline const OutlineNode* OutlineNode::parent() const { return m_parent; } inline int OutlineNode::indexOf(const OutlineNode* child) const { const auto max = m_children.size(); // Comparing the address here is only fine since we never modify the vector after initial creation for (size_t i = 0; i < max; i++) { if (child == &m_children[i]) { return i; } } return -1; } inline QIcon OutlineNode::icon() const { return m_cachedIcon; } inline QString OutlineNode::text() const { return m_cachedText; } -inline KDevelop::DUChainBase* OutlineNode::duChainObject() +inline KDevelop::DUChainBase* OutlineNode::duChainObject() const { Q_ASSERT(KDevelop::DUChain::lock()->currentThreadHasReadLock()); return m_declOrContext.data(); } inline OutlineNode::OutlineNode(OutlineNode&& other) noexcept : m_cachedText(std::move(other.m_cachedText)) , m_cachedIcon(std::move(other.m_cachedIcon)) , m_declOrContext(std::move(other.m_declOrContext)) , m_parent(std::move(other.m_parent)) , m_children(std::move(other.m_children)) { // qDebug("Move ctor %p -> %p", &other, this); other.m_parent = nullptr; other.m_declOrContext = nullptr; for (OutlineNode& child : m_children) { // when we are moved the parent pointer has to be updated for the children! child.m_parent = this; } } inline OutlineNode& OutlineNode::operator=(OutlineNode&& other) noexcept { if (this == &other) { return *this; } m_cachedText = std::move(other.m_cachedText); m_cachedIcon = std::move(other.m_cachedIcon); m_declOrContext = std::move(other.m_declOrContext); m_parent = std::move(other.m_parent); m_children = std::move(other.m_children); // qDebug("Move assignment %p -> %p", &other, this); other.m_parent = nullptr; other.m_declOrContext = nullptr; for (OutlineNode& child : m_children) { // when we are moved the parent pointer has to be updated for the children! child.m_parent = this; } return *this; } inline void swap(OutlineNode& n1, OutlineNode& n2) { // For some reason std::sort only sometimes calls swap and mostly uses move ctor + assign. // Probably it uses different algorithms for different sequence sizes // qDebug("Swapping %p and %p", &n1, &n2); std::swap(n1.m_cachedText, n2.m_cachedText); std::swap(n1.m_cachedIcon, n2.m_cachedIcon); std::swap(n1.m_declOrContext, n2.m_declOrContext); std::swap(n1.m_parent, n2.m_parent); std::swap(n1.m_children, n2.m_children); }