Utils: improve readability of FileInProjectFinder

Preserve existing functionality, but split the large findFileOrDirectory
logic into check* helper methods

Change-Id: Ib7f1d1cebc001d014261616e15e4a87f6d01e8ac
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Tim Jenßen
2025-02-21 11:27:20 +01:00
committed by Tim Jenssen
parent 8e196c9e39
commit 3170bf47b5
2 changed files with 208 additions and 127 deletions

View File

@@ -15,8 +15,6 @@
#include <QMenu> #include <QMenu>
#include <QUrl> #include <QUrl>
#include <algorithm>
namespace { namespace {
static Q_LOGGING_CATEGORY(finderLog, "qtc.utils.fileinprojectfinder", QtWarningMsg); static Q_LOGGING_CATEGORY(finderLog, "qtc.utils.fileinprojectfinder", QtWarningMsg);
} }
@@ -161,24 +159,32 @@ bool FileInProjectFinder::handleSuccess(const FilePath &originalPath, const File
return true; return true;
} }
bool FileInProjectFinder::findFileOrDirectory(const FilePath &originalPath, FileHandler fileHandler, bool FileInProjectFinder::checkRootDirectory(const FilePath &originalPath,
DirectoryHandler directoryHandler) const DirectoryHandler directoryHandler) const
{ {
if (originalPath.isEmpty()) { if (!directoryHandler)
qCDebug(finderLog) << "FileInProjectFinder: malformed original path, returning";
return false; return false;
}
if (directoryHandler && originalPath == m_projectDir && originalPath.isDir()) { if (originalPath != m_projectDir || !originalPath.isDir())
const QStringList realEntries = QDir(originalPath.toFSPathString()) return false;
.entryList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
directoryHandler(realEntries, originalPath.toFSPathString().size()); const QString originalFSPath = originalPath.toFSPathString();
return true; const QStringList realEntries = QDir(originalFSPath)
} .entryList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
directoryHandler(realEntries, originalFSPath.size());
return true;
}
bool FileInProjectFinder::checkMappedPath(const FilePath &originalPath,
FileHandler fileHandler,
DirectoryHandler directoryHandler) const
{
const QString origFsPath = originalPath.toFSPathString();
const auto segments = origFsPath.split('/', Qt::SkipEmptyParts);
const auto segments = originalPath.toFSPathString().split('/', Qt::SkipEmptyParts);
const PathMappingNode *node = &m_pathMapRoot; const PathMappingNode *node = &m_pathMapRoot;
for (const auto &segment : segments) { for (const QString &segment : segments) {
auto it = node->children.find(segment); auto it = node->children.find(segment);
if (it == node->children.end()) { if (it == node->children.end()) {
node = nullptr; node = nullptr;
@@ -186,146 +192,204 @@ bool FileInProjectFinder::findFileOrDirectory(const FilePath &originalPath, File
} }
node = *it; node = *it;
} }
if (!node)
return false;
const int origLength = originalPath.toFSPathString().length(); const int origLength = origFsPath.size();
if (node) {
if (!node->localPath.isEmpty()) { if (!node->localPath.isEmpty()) {
if (checkPath(node->localPath, origLength, fileHandler, directoryHandler)) { if (checkPath(node->localPath, origLength, fileHandler, directoryHandler)) {
return handleSuccess(originalPath, {node->localPath}, origLength, return handleSuccess(originalPath, {node->localPath}, origLength, "in mapped paths");
"in mapped paths");
}
} else if (directoryHandler) {
directoryHandler(node->children.keys(), origLength);
qCDebug(finderLog) << "FileInProjectFinder: found virtual directory" << originalPath
<< "in mapped paths";
return true;
} }
} else if (directoryHandler) {
directoryHandler(node->children.keys(), origLength);
qCDebug(finderLog) << "FileInProjectFinder: found virtual directory" << originalPath
<< "in mapped paths";
return true;
} }
return false;
}
bool FileInProjectFinder::checkCache(const FilePath &originalPath,
FileHandler fileHandler,
DirectoryHandler directoryHandler) const
{
auto it = m_cache.find(originalPath); auto it = m_cache.find(originalPath);
if (it != m_cache.end()) { if (it == m_cache.end())
qCDebug(finderLog) << "FileInProjectFinder: checking cache ..."; return false;
// check if cached path is still there
CacheEntry &candidate = it.value();
for (auto pathIt = candidate.paths.begin(); pathIt != candidate.paths.end();) {
if (checkPath(*pathIt, candidate.matchLength, fileHandler, directoryHandler)) {
qCDebug(finderLog) << "FileInProjectFinder: found" << *pathIt << "in the cache";
++pathIt;
} else {
pathIt = candidate.paths.erase(pathIt);
}
}
if (!candidate.paths.empty())
return true;
m_cache.erase(it);
}
if (!m_projectDir.isEmpty()) { qCDebug(finderLog) << "FileInProjectFinder: checking cache ...";
qCDebug(finderLog) << "FileInProjectFinder: checking project directory ..."; CacheEntry &candidate = it.value();
for (auto pathIt = candidate.paths.begin(); pathIt != candidate.paths.end();) {
int prefixToIgnore = -1; if (checkPath(*pathIt, candidate.matchLength, fileHandler, directoryHandler)) {
const QChar separator = QLatin1Char('/'); qCDebug(finderLog) << "FileInProjectFinder: found" << *pathIt << "in the cache";
if (originalPath.startsWith(m_projectDir.toFSPathString() + separator)) { ++pathIt;
if (originalPath.osType() == OsTypeMac) { } else {
// starting with the project path is not sufficient if the file was pathIt = candidate.paths.erase(pathIt);
// copied in an insource build, e.g. into MyApp.app/Contents/Resources
static const QString appResourcePath = QString::fromLatin1(".app/Contents/Resources");
if (originalPath.contains(appResourcePath)) {
// the path is inside the project, but most probably as a resource of an insource build
// so ignore that path
prefixToIgnore = originalPath.toFSPathString().indexOf(appResourcePath) + appResourcePath.length();
}
}
if (prefixToIgnore == -1
&& checkPath(originalPath, origLength, fileHandler, directoryHandler)) {
return handleSuccess(originalPath, {originalPath}, origLength,
"in project directory");
}
}
qCDebug(finderLog) << "FileInProjectFinder:"
<< "checking stripped paths in project directory ...";
// Strip directories one by one from the beginning of the path,
// and see if the new relative path exists in the build directory.
if (prefixToIgnore < 0) {
if (!originalPath.isAbsolutePath()
&& !originalPath.startsWith(separator)) {
prefixToIgnore = 0;
} else {
prefixToIgnore = originalPath.toFSPathString().indexOf(separator);
}
}
while (prefixToIgnore != -1) {
QString candidateString = originalPath.toFSPathString();
candidateString.remove(0, prefixToIgnore);
candidateString.prepend(m_projectDir.toUrlishString());
const FilePath candidate = FilePath::fromString(candidateString);
const int matchLength = origLength - prefixToIgnore;
// FIXME: This might be a worse match than what we find later.
if (checkPath(candidate, matchLength, fileHandler, directoryHandler)) {
return handleSuccess(originalPath, {candidate}, matchLength,
"in project directory");
}
prefixToIgnore = originalPath.toUrlishString().indexOf(separator, prefixToIgnore + 1);
} }
} }
// find best matching file path in project files if (!candidate.paths.empty())
return true;
m_cache.erase(it);
return false;
}
bool FileInProjectFinder::checkProjectDirectory(const FilePath &originalPath,
FileHandler fileHandler,
DirectoryHandler directoryHandler) const
{
if (m_projectDir.isEmpty())
return false;
qCDebug(finderLog) << "FileInProjectFinder: checking project directory ...";
const QString origFsPath = originalPath.toFSPathString();
const int origLength = origFsPath.size();
const QChar separator = QLatin1Char('/');
int prefixToIgnore = -1;
if (originalPath.startsWith(m_projectDir.toFSPathString() + separator)) {
if (originalPath.osType() == OsTypeMac) {
// starting with the project path is not sufficient if the file was
// copied in an insource build, e.g. into MyApp.app/Contents/Resources
static const QString appResourcePath = QString::fromLatin1(".app/Contents/Resources");
if (originalPath.contains(appResourcePath)) {
// the path is inside the project, but most probably as a resource of an insource build
// so ignore that path
prefixToIgnore = origFsPath.indexOf(appResourcePath) + appResourcePath.length();
}
}
if (prefixToIgnore == -1
&& checkPath(originalPath, origLength, fileHandler, directoryHandler)) {
return handleSuccess(originalPath, {originalPath}, origLength, "in project directory");
}
}
qCDebug(finderLog) << "FileInProjectFinder: checking stripped paths in project directory ...";
// Strip directories one by one from the beginning of the path,
// and see if the new relative path exists in the build directory.
if (prefixToIgnore < 0) {
if (!originalPath.isAbsolutePath() && !originalPath.startsWith(separator)) {
prefixToIgnore = 0;
} else {
prefixToIgnore = origFsPath.indexOf(separator);
}
}
// Repeatedly strip directories from the front
while (prefixToIgnore != -1) {
QString candidateString = origFsPath;
candidateString.remove(0, prefixToIgnore);
candidateString.prepend(m_projectDir.toUrlishString());
const FilePath candidate = FilePath::fromString(candidateString);
const int matchLength = origLength - prefixToIgnore;
// FIXME: This might be a worse match than what we find later.
if (checkPath(candidate, matchLength, fileHandler, directoryHandler)) {
return handleSuccess(originalPath, {candidate}, matchLength, "in project directory");
}
prefixToIgnore = originalPath.toUrlishString().indexOf(separator, prefixToIgnore + 1);
}
return false;
}
bool FileInProjectFinder::checkProjectFiles(const FilePath &originalPath,
FileHandler fileHandler,
DirectoryHandler directoryHandler) const
{
qCDebug(finderLog) << "FileInProjectFinder: checking project files ..."; qCDebug(finderLog) << "FileInProjectFinder: checking project files ...";
QStringList matches;
const QString lastSegment = originalPath.fileName(); const QString lastSegment = originalPath.fileName();
QStringList matches;
if (fileHandler) if (fileHandler)
matches.append(filesWithSameFileName(lastSegment)); matches.append(filesWithSameFileName(lastSegment));
if (directoryHandler) if (directoryHandler)
matches.append(pathSegmentsWithSameName(lastSegment)); matches.append(pathSegmentsWithSameName(lastSegment));
const QStringList matchedFilePaths = bestMatches(matches, originalPath.toUrlishString()); const QStringList matchedFilePaths = bestMatches(matches, originalPath.toUrlishString());
if (!matchedFilePaths.empty()) { if (matchedFilePaths.isEmpty())
const int matchLength = commonPostFixLength(matchedFilePaths.first(), originalPath.toUrlishString()); return false;
FilePaths hits;
for (const QString &matchedFilePath : matchedFilePaths) { const int matchLength = commonPostFixLength(matchedFilePaths.first(), originalPath.toUrlishString());
if (checkPath(FilePath::fromString(matchedFilePath), matchLength, fileHandler, directoryHandler)) FilePaths hits;
hits.append(FilePath::fromString(matchedFilePath)); for (const QString &matchedFilePath : matchedFilePaths) {
} if (checkPath(FilePath::fromString(matchedFilePath), matchLength, fileHandler, directoryHandler))
if (!hits.empty()) hits.append(FilePath::fromString(matchedFilePath));
return handleSuccess(originalPath, hits, matchLength, "when matching project files");
} }
if (hits.isEmpty())
return false;
CacheEntry foundPath = findInSearchPaths(originalPath, fileHandler, directoryHandler); return handleSuccess(originalPath, hits, matchLength, "when matching project files");
if (!foundPath.paths.isEmpty()) { }
return handleSuccess(originalPath, foundPath.paths, foundPath.matchLength,
"in search path");
}
qCDebug(finderLog) << "FileInProjectFinder: checking absolute path in sysroot ..."; bool FileInProjectFinder::checkSearchPaths(const FilePath &originalPath,
FileHandler fileHandler,
DirectoryHandler directoryHandler) const
{
for (const FilePath &dirPath : m_searchDirectories) {
const CacheEntry found = findInSearchPath(dirPath, originalPath,
fileHandler, directoryHandler);
// check if absolute path is found in sysroot if (!found.paths.isEmpty()) {
if (!m_sysroot.isEmpty()) { return handleSuccess(originalPath, found.paths, found.matchLength,
const FilePath sysrootPath = m_sysroot.pathAppended(originalPath.toUrlishString()); "in search path");
if (checkPath(sysrootPath, origLength, fileHandler, directoryHandler)) {
return handleSuccess(originalPath, {sysrootPath}, origLength, "in sysroot");
} }
} }
qCDebug(finderLog) << "FileInProjectFinder: couldn't find file!";
return false; return false;
} }
FileInProjectFinder::CacheEntry FileInProjectFinder::findInSearchPaths( bool FileInProjectFinder::checkSysroot(const FilePath &originalPath,
const FilePath &filePath, FileHandler fileHandler, DirectoryHandler directoryHandler) const FileHandler fileHandler,
DirectoryHandler directoryHandler) const
{ {
for (const FilePath &dirPath : m_searchDirectories) { qCDebug(finderLog) << "FileInProjectFinder: checking absolute path in sysroot ...";
const CacheEntry found = findInSearchPath(dirPath, filePath,
fileHandler, directoryHandler); if (m_sysroot.isEmpty())
if (!found.paths.isEmpty()) return false;
return found;
const int origLength = originalPath.toFSPathString().length();
const FilePath sysrootPath = m_sysroot.pathAppended(originalPath.toUrlishString());
if (!checkPath(sysrootPath, origLength, fileHandler, directoryHandler))
return false;
return handleSuccess(originalPath, {sysrootPath}, origLength, "in sysroot");
}
bool FileInProjectFinder::findFileOrDirectory(const FilePath &originalPath,
FileHandler fileHandler,
DirectoryHandler directoryHandler) const
{
if (originalPath.isEmpty()) {
qCDebug(finderLog) << "FileInProjectFinder: malformed original path, returning";
return false;
} }
return CacheEntry(); if (checkRootDirectory(originalPath, directoryHandler))
return true;
if (checkMappedPath(originalPath, fileHandler, directoryHandler))
return true;
if (checkCache(originalPath, fileHandler, directoryHandler))
return true;
if (checkProjectDirectory(originalPath, fileHandler, directoryHandler))
return true;
if (checkProjectFiles(originalPath, fileHandler, directoryHandler))
return true;
if (checkSearchPaths(originalPath, fileHandler, directoryHandler))
return true;
if (checkSysroot(originalPath, fileHandler, directoryHandler))
return true;
qCDebug(finderLog) << "FileInProjectFinder: couldn't find file!";
return false;
} }
static QString chopFirstDir(const QString &dirPath) static QString chopFirstDir(const QString &dirPath)

View File

@@ -62,13 +62,30 @@ private:
mutable QHash<FilePath, std::shared_ptr<QrcParser>> m_parserCache; mutable QHash<FilePath, std::shared_ptr<QrcParser>> m_parserCache;
}; };
CacheEntry findInSearchPaths(const FilePath &filePath, FileHandler fileHandler,
DirectoryHandler directoryHandler) const;
static CacheEntry findInSearchPath(const FilePath &searchPath, const FilePath &filePath, static CacheEntry findInSearchPath(const FilePath &searchPath, const FilePath &filePath,
FileHandler fileHandler, DirectoryHandler directoryHandler); FileHandler fileHandler, DirectoryHandler directoryHandler);
QStringList filesWithSameFileName(const QString &fileName) const; QStringList filesWithSameFileName(const QString &fileName) const;
QStringList pathSegmentsWithSameName(const QString &path) const; QStringList pathSegmentsWithSameName(const QString &path) const;
bool checkRootDirectory(const FilePath &originalPath, DirectoryHandler directoryHandler) const;
bool checkMappedPath(const FilePath &originalPath,
FileHandler fileHandler,
DirectoryHandler directoryHandler) const;
bool checkCache(const FilePath &originalPath,
FileHandler fileHandler,
DirectoryHandler directoryHandler) const;
bool checkProjectDirectory(const FilePath &originalPath,
FileHandler fileHandler,
DirectoryHandler directoryHandler) const;
bool checkProjectFiles(const FilePath &originalPath,
FileHandler fileHandler,
DirectoryHandler directoryHandler) const;
bool checkSearchPaths(const FilePath &originalPath,
FileHandler fileHandler,
DirectoryHandler directoryHandler) const;
bool checkSysroot(const FilePath &originalPath,
FileHandler fileHandler,
DirectoryHandler directoryHandler) const;
bool handleSuccess(const FilePath &originalPath, const FilePaths &found, int confidence, bool handleSuccess(const FilePath &originalPath, const FilePaths &found, int confidence,
const char *where) const; const char *where) const;