File indexing completed on 2024-05-12 04:39:12
0001 /* 0002 SPDX-FileCopyrightText: 2014 Sergey Kalinichev <kalinichev.so.0@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "documentfinderhelpers.h" 0008 0009 #include "duchain/clanghelpers.h" 0010 0011 #include <language/duchain/duchain.h> 0012 #include <language/duchain/declaration.h> 0013 #include <language/duchain/functiondeclaration.h> 0014 #include <language/duchain/functiondefinition.h> 0015 #include <language/duchain/duchainutils.h> 0016 0017 #include <KDesktopFile> 0018 0019 using namespace KDevelop; 0020 0021 namespace { 0022 0023 enum FileType { 0024 Unknown, ///< Doesn't belong to C++ 0025 Header, ///< Is a header file 0026 Source ///< Is a C(++) file 0027 }; 0028 0029 class PotentialBuddyCollector : public DUChainUtils::DUChainItemFilter 0030 { 0031 public: 0032 enum BuddyMode { 0033 Header, 0034 Source 0035 }; 0036 0037 explicit PotentialBuddyCollector(BuddyMode mode) 0038 : mode(mode) 0039 {} 0040 0041 bool accept(Declaration* decl) override 0042 { 0043 if (decl->range().isEmpty()) 0044 return false; 0045 0046 if (mode == Header && decl->isFunctionDeclaration()) { 0047 // Search for definitions of our declarations 0048 auto* def = FunctionDefinition::definition(decl); 0049 if (def) { 0050 vote(def->url().toUrl()); 0051 } 0052 0053 return true; 0054 } 0055 else if (mode == Source && decl->isFunctionDeclaration()) { 0056 auto* fdef = dynamic_cast<FunctionDefinition*>(decl); 0057 if (fdef) { 0058 Declaration* fdecl = fdef->declaration(); 0059 if (fdecl) { 0060 vote(fdecl->url().toUrl()); 0061 } 0062 } 0063 0064 return true; 0065 } else { 0066 return false; 0067 } 0068 } 0069 bool accept(DUContext* ctx) override 0070 { 0071 if (ctx->type() == DUContext::Class || ctx->type() == DUContext::Namespace || ctx->type() == DUContext::Global || ctx->type() == DUContext::Other || ctx->type() == DUContext::Helper ) { 0072 return true; 0073 } else { 0074 return false; 0075 } 0076 } 0077 0078 QUrl bestBuddy() const 0079 { 0080 QUrl ret; 0081 int bestCount = 0; 0082 for (auto it = m_buddyFiles.begin(); it != m_buddyFiles.end(); ++it) { 0083 if(it.value() > bestCount) { 0084 bestCount = it.value(); 0085 ret = it.key(); 0086 } 0087 } 0088 0089 return ret; 0090 } 0091 private: 0092 BuddyMode mode; 0093 QHash<QUrl, int> m_buddyFiles; 0094 0095 void vote(const QUrl& url) 0096 { 0097 m_buddyFiles[url]++; 0098 } 0099 }; 0100 0101 /** 0102 * Tries to find a buddy file to the given file by looking at the DUChain. 0103 * 0104 * The project might keep source files separate from headers. To cover 0105 * this situation, we examine DUChain for the most probable buddy file. 0106 * This of course only works if we have parsed the buddy file, but it is 0107 * better than nothing. 0108 * 0109 * @param url url of the source/header file to find a buddy for 0110 * @param type type of the file @p url 0111 * 0112 * @returns QUrl of the most probable buddy file, or an empty url 0113 **/ 0114 QUrl duchainBuddyFile(const QUrl& url, FileType type) 0115 { 0116 DUChainReadLocker lock; 0117 0118 auto ctx = DUChainUtils::standardContextForUrl(url); 0119 if (ctx) { 0120 PotentialBuddyCollector collector( type == Header ? PotentialBuddyCollector::Header : PotentialBuddyCollector::Source ); 0121 DUChainUtils::collectItems(ctx, collector); 0122 0123 return collector.bestBuddy(); 0124 } 0125 0126 return QUrl(); 0127 } 0128 0129 /** 0130 * Generates the base path (without extension) and the file type 0131 * for the specified url. 0132 * 0133 * @returns pair of base path and file type which has been found for @p url. 0134 */ 0135 QPair<QString,FileType> basePathAndTypeForUrl(const QUrl &url) 0136 { 0137 QString path = url.toLocalFile(); 0138 int idxSlash = path.lastIndexOf(QLatin1Char('/')); 0139 int idxDot = path.lastIndexOf(QLatin1Char('.')); 0140 FileType fileType = Unknown; 0141 QString basePath; 0142 if (idxSlash >= 0 && idxDot >= 0 && idxDot > idxSlash) { 0143 basePath = path.left(idxDot); 0144 if (idxDot + 1 < path.length()) { 0145 QString extension = path.mid(idxDot + 1); 0146 if (ClangHelpers::isHeader(extension)) { 0147 fileType = Header; 0148 } else if (ClangHelpers::isSource(extension)) { 0149 fileType = Source; 0150 } 0151 } 0152 } else { 0153 basePath = path; 0154 } 0155 0156 return qMakePair(basePath, fileType); 0157 } 0158 0159 } 0160 0161 namespace DocumentFinderHelpers { 0162 QStringList mimeTypesList() 0163 { 0164 static const QStringList mimeTypes = { 0165 QStringLiteral("text/x-chdr"), 0166 QStringLiteral("text/x-c++hdr"), 0167 QStringLiteral("text/vnd.nvidia.cuda.chdr"), 0168 QStringLiteral("text/x-csrc"), 0169 QStringLiteral("text/x-c++src"), 0170 QStringLiteral("text/vnd.nvidia.cuda.csrc"), 0171 QStringLiteral("text/x-objcsrc") 0172 }; 0173 return mimeTypes; 0174 } 0175 0176 bool areBuddies(const QUrl &url1, const QUrl& url2) 0177 { 0178 auto type1 = basePathAndTypeForUrl(url1); 0179 auto type2 = basePathAndTypeForUrl(url2); 0180 0181 QUrl headerPath; 0182 QUrl sourcePath; 0183 0184 // Check that one file is a header, the other one is source 0185 if (type1.second == Header && type2.second == Source) { 0186 headerPath = url1; 0187 sourcePath = url2; 0188 } else if (type1.second == Source && type2.second == Header) { 0189 headerPath = url2; 0190 sourcePath = url1; 0191 } else { 0192 // Some other file constellation 0193 return false; 0194 } 0195 0196 // The simplest directory layout is with header + source in one directory. 0197 // So check that first. 0198 if (type1.first == type2.first) { 0199 return true; 0200 } 0201 0202 // Also check if the DUChain thinks this is likely 0203 if (duchainBuddyFile(sourcePath, Source) == headerPath) { 0204 return true; 0205 } 0206 0207 return false; 0208 } 0209 0210 bool buddyOrder(const QUrl &url1, const QUrl& url2) 0211 { 0212 auto type1 = basePathAndTypeForUrl(url1); 0213 auto type2 = basePathAndTypeForUrl(url2); 0214 // Precondition is that the two URLs are buddies, so don't check it 0215 return(type1.second == Header && type2.second == Source); 0216 } 0217 0218 QVector<QUrl> potentialBuddies(const QUrl& url, bool checkDUChain) 0219 { 0220 auto type = basePathAndTypeForUrl(url); 0221 // Don't do anything for types we don't know 0222 if (type.second == Unknown) { 0223 return {}; 0224 } 0225 0226 // Depending on the buddy's file type we either generate source extensions (for headers) 0227 // or header extensions (for sources) 0228 const auto& extensions = ( type.second == Header ? ClangHelpers::sourceExtensions() : ClangHelpers::headerExtensions() ); 0229 QVector< QUrl > buddies; 0230 buddies.reserve(extensions.size()); 0231 for(const QString& extension : extensions) { 0232 if (!extension.contains(QLatin1Char('.'))) { 0233 buddies.append(QUrl::fromLocalFile(type.first + QLatin1Char('.') + extension)); 0234 } else { 0235 buddies.append(QUrl::fromLocalFile(type.first + extension)); 0236 } 0237 } 0238 0239 if (checkDUChain) { 0240 // Also ask DUChain for a guess 0241 QUrl bestBuddy = duchainBuddyFile(url, type.second); 0242 if (!buddies.contains(bestBuddy)) { 0243 buddies.append(bestBuddy); 0244 } 0245 } 0246 0247 return buddies; 0248 } 0249 0250 QString sourceForHeader(const QString& headerPath) 0251 { 0252 if (!ClangHelpers::isHeader(headerPath)) { 0253 return {}; 0254 } 0255 0256 QString targetUrl; 0257 const auto buddies = DocumentFinderHelpers::potentialBuddies(QUrl::fromLocalFile(headerPath)); 0258 for (const auto& buddy : buddies) { 0259 const auto local = buddy.toLocalFile(); 0260 if (QFileInfo::exists(local)) { 0261 targetUrl = local; 0262 break; 0263 } 0264 } 0265 0266 return targetUrl; 0267 } 0268 0269 }