File indexing completed on 2024-05-05 16:46:04
0001 /* 0002 SPDX-FileCopyrightText: 2010, 2015 Alex Richardson <alex.richardson@gmx.de> 0003 0004 SPDX-License-Identifier: LGPL-2.1-or-later 0005 */ 0006 0007 #include "outlinenode.h" 0008 0009 #include <language/duchain/duchainutils.h> 0010 #include <language/duchain/ducontext.h> 0011 #include <language/duchain/declaration.h> 0012 #include <language/duchain/duchain.h> 0013 #include <language/duchain/duchainlock.h> 0014 #include <language/duchain/classfunctiondeclaration.h> 0015 #include <language/duchain/functiondefinition.h> 0016 #include <language/duchain/types/alltypes.h> 0017 #include <language/duchain/namespacealiasdeclaration.h> 0018 #include <language/duchain/classdeclaration.h> 0019 #include <language/duchain/forwarddeclaration.h> 0020 0021 #include <KTextEditor/CodeCompletionModel> 0022 0023 #include <debug.h> 0024 0025 using namespace KDevelop; 0026 0027 OutlineNode::OutlineNode(const QString& text, OutlineNode* parent) 0028 : m_cachedText(text) 0029 , m_parent(parent) 0030 { 0031 } 0032 0033 OutlineNode::OutlineNode(DUContext* ctx, const QString& name, OutlineNode* parent) 0034 : m_cachedText(name) 0035 , m_declOrContext(ctx) 0036 , m_parent(parent) 0037 { 0038 KTextEditor::CodeCompletionModel::CompletionProperties prop; 0039 switch (ctx->type()) { 0040 case KDevelop::DUContext::Class: 0041 prop |= KTextEditor::CodeCompletionModel::Class; 0042 break; 0043 case KDevelop::DUContext::Enum: 0044 prop |= KTextEditor::CodeCompletionModel::Enum; 0045 break; 0046 case KDevelop::DUContext::Function: 0047 prop |= KTextEditor::CodeCompletionModel::Function; 0048 break; 0049 case KDevelop::DUContext::Namespace: 0050 prop |= KTextEditor::CodeCompletionModel::Namespace; 0051 break; 0052 case KDevelop::DUContext::Template: 0053 prop |= KTextEditor::CodeCompletionModel::Template; 0054 break; 0055 default: 0056 break; 0057 } 0058 m_cachedIcon = DUChainUtils::iconForProperties(prop); 0059 appendContext(ctx, ctx->topContext()); 0060 } 0061 0062 0063 OutlineNode::OutlineNode(Declaration* decl, OutlineNode* parent) 0064 : m_declOrContext(decl) 0065 , m_parent(parent) 0066 { 0067 // qCDebug(PLUGIN_OUTLINE) << "Adding:" << decl->qualifiedIdentifier().toString() << ": " <<typeid(*decl).name(); 0068 0069 // TODO: properly qualified identifier for out of line function definitions 0070 m_cachedText = decl->identifier().toString(); 0071 m_cachedIcon = DUChainUtils::iconForDeclaration(decl); 0072 if (auto* alias = dynamic_cast<NamespaceAliasDeclaration*>(decl)) { 0073 //e.g. C++ using namespace statement 0074 m_cachedText = alias->importIdentifier().toString(); 0075 } 0076 else if (auto* member = dynamic_cast<ClassMemberDeclaration*>(decl)) { 0077 if (member->isFriend()) { 0078 m_cachedText = QLatin1String("friend ") + m_cachedText; 0079 } 0080 } 0081 if (AbstractType::Ptr type = decl->abstractType()) { 0082 //add the (function return) type at the end (after a colon - like UML) 0083 //so that the first thing seen is the name of the function/variable 0084 //and not the (function return) type 0085 AbstractType::WhichType typeEnum = type->whichType(); 0086 switch (typeEnum) { 0087 case AbstractType::TypeFunction: { 0088 auto func = type.staticCast<FunctionType>(); 0089 // func->partToString() does not add the argument names -> do it manually 0090 if (DUContext* fCtx = DUChainUtils::functionContext(decl)) { 0091 m_cachedText += QLatin1Char('('); 0092 bool first = true; 0093 const auto childDecls = fCtx->localDeclarations(decl->topContext()); 0094 for (Declaration* childDecl : childDecls) { 0095 if (first) { 0096 first = false; 0097 } else { 0098 m_cachedText += QLatin1String(", "); 0099 } 0100 0101 if (childDecl->abstractType()) { 0102 m_cachedText += childDecl->abstractType()->toString(); 0103 } 0104 auto ident = childDecl->identifier(); 0105 if (!ident.isEmpty()) { 0106 m_cachedText += QLatin1Char(' ') + ident.toString(); 0107 } 0108 0109 } 0110 m_cachedText += QLatin1Char(')'); 0111 } else { 0112 qCWarning(PLUGIN_OUTLINE) << "Missing function context:" << decl->qualifiedIdentifier().toString(); 0113 m_cachedText += func->partToString(FunctionType::SignatureArguments); 0114 } 0115 //constructors/destructors have no return type, a trailing semicolon would look stupid 0116 if (func->returnType()) { 0117 m_cachedText += QLatin1String(" : ") + func->partToString(FunctionType::SignatureReturn); 0118 } 0119 return; // don't append any children here! 0120 } 0121 case AbstractType::TypeEnumeration: 0122 //no need to append the fully qualified type 0123 break; 0124 case AbstractType::TypeEnumerator: 0125 //no need to append the fully qualified type 0126 Q_ASSERT(decl->type<EnumeratorType>()); 0127 m_cachedText += QLatin1String(" = ") + decl->type<EnumeratorType>()->valueAsString(); 0128 break; 0129 case AbstractType::TypeStructure: { 0130 //this seems to be the way it has to be done (after grepping through source code) 0131 //TODO shouldn't there be some kind of isFriend() functionality? 0132 static IndexedIdentifier friendIdentifier(Identifier(QStringLiteral("friend"))); 0133 const bool isFriend = decl->indexedIdentifier() == friendIdentifier; 0134 if (isFriend) { 0135 //FIXME There seems to be no way of finding out whether the friend is class/struct/etc 0136 m_cachedText += QLatin1Char(' ') + type->toString(); 0137 } 0138 break; 0139 } 0140 case AbstractType::TypeAlias: { 0141 //append the type it aliases 0142 auto alias = type.staticCast<TypeAliasType>(); 0143 if (AbstractType::Ptr targetType = alias->type()) { 0144 m_cachedText += QLatin1String(" : ") + targetType->toString(); 0145 } 0146 } 0147 break; 0148 default: 0149 QString typeStr = type->toString(); 0150 if (!typeStr.isEmpty()) { 0151 m_cachedText += QLatin1String(" : ") + typeStr; 0152 } 0153 } 0154 } 0155 0156 0157 //these two don't seem to be hit 0158 if (decl->isAutoDeclaration()) { 0159 m_cachedText = QLatin1String("Implicit: ") + m_cachedText; 0160 } 0161 if (decl->isAnonymous()) { 0162 m_cachedText = QLatin1String("<anonymous>") + m_cachedText; 0163 } 0164 0165 if (DUContext* ctx = decl->internalContext()) { 0166 appendContext(ctx, decl->topContext()); 0167 } 0168 if (m_cachedText.isEmpty()) { 0169 m_cachedText = i18nc("An anonymous declaration (class, function, etc.)", "<anonymous>"); 0170 } 0171 } 0172 0173 std::unique_ptr<OutlineNode> OutlineNode::dummyNode() 0174 { 0175 return std::unique_ptr<OutlineNode>(new OutlineNode(QStringLiteral("<dummy node>"), nullptr)); 0176 } 0177 0178 std::unique_ptr<OutlineNode> OutlineNode::fromTopContext(TopDUContext* ctx) 0179 { 0180 auto result = dummyNode(); 0181 result->appendContext(ctx, ctx); 0182 return result; 0183 } 0184 0185 void OutlineNode::appendContext(DUContext* ctx, TopDUContext* top) 0186 { 0187 // qDebug() << ctx->scopeIdentifier().toString() << "context type=" << ctx->type(); 0188 const auto childDecls = ctx->localDeclarations(top); 0189 for (Declaration* childDecl : childDecls) { 0190 if (childDecl) { 0191 m_children.emplace_back(childDecl, this); 0192 } 0193 } 0194 bool certainlyRequiresSorting = false; 0195 const auto childContexts = ctx->childContexts(); 0196 for (DUContext* childContext : childContexts) { 0197 if (childContext->owner()) { 0198 // if there is a onwner, this will already have been handled by the loop above 0199 // TODO: is this always true? With my testing so far it seems to be 0200 // qDebug() << childContext->scopeIdentifier(true).toString() 0201 // << " has an owner declaration: " << childContext->owner()->toString() << "-> skip"; 0202 continue; 0203 } 0204 QVector<Declaration*> decls = childContext->localDeclarations(top); 0205 if (decls.isEmpty()) { 0206 continue; 0207 } 0208 // we now know that we will have o sort since we appended a node in the wrong order 0209 certainlyRequiresSorting = true; 0210 QString ctxName = childContext->scopeIdentifier(true).toString(); 0211 // if child context is a template context or if name is empty append to current list, 0212 // otherwise create a new context node 0213 if (childContext->type() == DUContext::Template || ctxName.isEmpty()) { 0214 //append all subcontexts to this node 0215 appendContext(childContext, top); 0216 } else { 0217 // context without matching declaration, for example the definition of 0218 // "class Foo::Bar if it was forward declared in a namespace before: 0219 // namespace Foo { class Bar; } 0220 // class Foo::Bar { ... }; 0221 // TODO: icon and location for the namespace 0222 if (childContext->type() == DUContext::ContextType::Helper) { 0223 // This context could be for a definition of an existing class method. 0224 // If we don't merge all those context end up with a tree like this: 0225 // +-+- FooClass 0226 // | \-- method1() 0227 // +-+- FooClass 0228 // | \-- method2() 0229 // \ OtherStuff 0230 auto it = std::find_if(m_children.begin(), m_children.end(), [childContext](const OutlineNode& node) { 0231 if (auto* ctx = dynamic_cast<DUContext*>(node.duChainObject())) { 0232 return ctx->equalScopeIdentifier(childContext); 0233 } 0234 return false; 0235 }); 0236 if (it != m_children.end()) { 0237 it->appendContext(childContext, top); 0238 } 0239 else { 0240 // TODO: get the correct icon for the context 0241 m_children.emplace_back(childContext, ctxName, this); 0242 } 0243 } else { 0244 // just add the context 0245 m_children.emplace_back(childContext, ctxName, this); 0246 } 0247 } 0248 } 0249 // we now need to sort since sometimes the elements from ctx->localDeclarations(top) 0250 // are not in the order they appear in the source. Additionally, if we had any child 0251 // contexts that were added, they will be at the end of the list 0252 // and need to be moved to the correct location. In that case certainlyRequiresSorting 0253 // will be true and we can pass it to sortByLocation() to skip the std::is_sorted() call 0254 sortByLocation(certainlyRequiresSorting); 0255 } 0256 0257 void OutlineNode::sortByLocation(bool requiresSorting) 0258 { 0259 0260 if (m_children.size() <= 1) { 0261 return; 0262 } 0263 // TODO: does it make sense to cache m_declOrContext->range().start? 0264 // adds 8 bytes to each node, but save a lot of pointer lookups when sorting 0265 // qDebug("sorting children of %s (%p) by location", qPrintable(m_cachedText), this); 0266 auto compare = [](const OutlineNode& n1, const OutlineNode& n2) -> bool { 0267 // nodes without decl always go at the end 0268 if (!n1.m_declOrContext) { 0269 return false; 0270 } else if (!n2.m_declOrContext) { 0271 return true; 0272 } 0273 return n1.m_declOrContext->range().start < n2.m_declOrContext->range().start; 0274 }; 0275 // since most nodes will be correctly sorted we check that before calling std::sort(). 0276 // This saves a lot of move ctor/assignment calls in the common case. 0277 // If we appended a context without a Declaration* we know that it will be unsorted 0278 // so we can pass requiresSorting = true to skip the useless std::is_sorted() call. 0279 // uncomment the following qDebug() lines to see whether this optimization really makes sense 0280 if (requiresSorting || !std::is_sorted(m_children.begin(), m_children.end(), compare)) { 0281 // qDebug("Need to sort node %s(%p)", qPrintable(m_cachedText), this); 0282 std::sort(m_children.begin(), m_children.end(), compare); 0283 } else { 0284 // qDebug("Node %s(%p) was sorted!", qPrintable(m_cachedText), this); 0285 } 0286 } 0287 0288 OutlineNode::~OutlineNode() 0289 { 0290 }