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;
const QString originalFSPath = originalPath.toFSPathString();
const QStringList realEntries = QDir(originalFSPath)
.entryList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); .entryList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
directoryHandler(realEntries, originalPath.toFSPathString().size());
directoryHandler(realEntries, originalFSPath.size());
return true; return true;
} }
const auto segments = originalPath.toFSPathString().split('/', Qt::SkipEmptyParts); bool FileInProjectFinder::checkMappedPath(const FilePath &originalPath,
FileHandler fileHandler,
DirectoryHandler directoryHandler) const
{
const QString origFsPath = originalPath.toFSPathString();
const auto segments = origFsPath.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,13 +192,14 @@ bool FileInProjectFinder::findFileOrDirectory(const FilePath &originalPath, File
} }
node = *it; node = *it;
} }
if (!node)
return false;
const int origLength = origFsPath.size();
const int origLength = originalPath.toFSPathString().length();
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) { } else if (directoryHandler) {
directoryHandler(node->children.keys(), origLength); directoryHandler(node->children.keys(), origLength);
@@ -200,12 +207,18 @@ bool FileInProjectFinder::findFileOrDirectory(const FilePath &originalPath, File
<< "in mapped paths"; << "in mapped paths";
return true; 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())
return false;
qCDebug(finderLog) << "FileInProjectFinder: checking cache ..."; qCDebug(finderLog) << "FileInProjectFinder: checking cache ...";
// check if cached path is still there
CacheEntry &candidate = it.value(); CacheEntry &candidate = it.value();
for (auto pathIt = candidate.paths.begin(); pathIt != candidate.paths.end();) { for (auto pathIt = candidate.paths.begin(); pathIt != candidate.paths.end();) {
if (checkPath(*pathIt, candidate.matchLength, fileHandler, directoryHandler)) { if (checkPath(*pathIt, candidate.matchLength, fileHandler, directoryHandler)) {
@@ -215,16 +228,28 @@ bool FileInProjectFinder::findFileOrDirectory(const FilePath &originalPath, File
pathIt = candidate.paths.erase(pathIt); pathIt = candidate.paths.erase(pathIt);
} }
} }
if (!candidate.paths.empty()) if (!candidate.paths.empty())
return true; return true;
m_cache.erase(it); m_cache.erase(it);
return false;
} }
if (!m_projectDir.isEmpty()) { bool FileInProjectFinder::checkProjectDirectory(const FilePath &originalPath,
FileHandler fileHandler,
DirectoryHandler directoryHandler) const
{
if (m_projectDir.isEmpty())
return false;
qCDebug(finderLog) << "FileInProjectFinder: checking project directory ..."; qCDebug(finderLog) << "FileInProjectFinder: checking project directory ...";
int prefixToIgnore = -1; const QString origFsPath = originalPath.toFSPathString();
const int origLength = origFsPath.size();
const QChar separator = QLatin1Char('/'); const QChar separator = QLatin1Char('/');
int prefixToIgnore = -1;
if (originalPath.startsWith(m_projectDir.toFSPathString() + separator)) { if (originalPath.startsWith(m_projectDir.toFSPathString() + separator)) {
if (originalPath.osType() == OsTypeMac) { if (originalPath.osType() == OsTypeMac) {
// starting with the project path is not sufficient if the file was // starting with the project path is not sufficient if the file was
@@ -233,99 +258,138 @@ bool FileInProjectFinder::findFileOrDirectory(const FilePath &originalPath, File
if (originalPath.contains(appResourcePath)) { if (originalPath.contains(appResourcePath)) {
// the path is inside the project, but most probably as a resource of an insource build // the path is inside the project, but most probably as a resource of an insource build
// so ignore that path // so ignore that path
prefixToIgnore = originalPath.toFSPathString().indexOf(appResourcePath) + appResourcePath.length(); prefixToIgnore = origFsPath.indexOf(appResourcePath) + appResourcePath.length();
} }
} }
if (prefixToIgnore == -1 if (prefixToIgnore == -1
&& checkPath(originalPath, origLength, fileHandler, directoryHandler)) { && checkPath(originalPath, origLength, fileHandler, directoryHandler)) {
return handleSuccess(originalPath, {originalPath}, origLength, return handleSuccess(originalPath, {originalPath}, origLength, "in project directory");
"in project directory");
} }
} }
qCDebug(finderLog) << "FileInProjectFinder:" qCDebug(finderLog) << "FileInProjectFinder: checking stripped paths in project directory ...";
<< "checking stripped paths in project directory ...";
// Strip directories one by one from the beginning of the path, // Strip directories one by one from the beginning of the path,
// and see if the new relative path exists in the build directory. // and see if the new relative path exists in the build directory.
if (prefixToIgnore < 0) { if (prefixToIgnore < 0) {
if (!originalPath.isAbsolutePath() if (!originalPath.isAbsolutePath() && !originalPath.startsWith(separator)) {
&& !originalPath.startsWith(separator)) {
prefixToIgnore = 0; prefixToIgnore = 0;
} else { } else {
prefixToIgnore = originalPath.toFSPathString().indexOf(separator); prefixToIgnore = origFsPath.indexOf(separator);
} }
} }
// Repeatedly strip directories from the front
while (prefixToIgnore != -1) { while (prefixToIgnore != -1) {
QString candidateString = originalPath.toFSPathString(); QString candidateString = origFsPath;
candidateString.remove(0, prefixToIgnore); candidateString.remove(0, prefixToIgnore);
candidateString.prepend(m_projectDir.toUrlishString()); candidateString.prepend(m_projectDir.toUrlishString());
const FilePath candidate = FilePath::fromString(candidateString); const FilePath candidate = FilePath::fromString(candidateString);
const int matchLength = origLength - prefixToIgnore; const int matchLength = origLength - prefixToIgnore;
// FIXME: This might be a worse match than what we find later. // FIXME: This might be a worse match than what we find later.
if (checkPath(candidate, matchLength, fileHandler, directoryHandler)) { if (checkPath(candidate, matchLength, fileHandler, directoryHandler)) {
return handleSuccess(originalPath, {candidate}, matchLength, return handleSuccess(originalPath, {candidate}, matchLength, "in project directory");
"in project directory");
} }
prefixToIgnore = originalPath.toUrlishString().indexOf(separator, prefixToIgnore + 1); prefixToIgnore = originalPath.toUrlishString().indexOf(separator, prefixToIgnore + 1);
} }
return false;
} }
// find best matching file path in project files 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())
return false;
const int matchLength = commonPostFixLength(matchedFilePaths.first(), originalPath.toUrlishString()); const int matchLength = commonPostFixLength(matchedFilePaths.first(), originalPath.toUrlishString());
FilePaths hits; FilePaths hits;
for (const QString &matchedFilePath : matchedFilePaths) { for (const QString &matchedFilePath : matchedFilePaths) {
if (checkPath(FilePath::fromString(matchedFilePath), matchLength, fileHandler, directoryHandler)) if (checkPath(FilePath::fromString(matchedFilePath), matchLength, fileHandler, directoryHandler))
hits.append(FilePath::fromString(matchedFilePath)); hits.append(FilePath::fromString(matchedFilePath));
} }
if (!hits.empty()) if (hits.isEmpty())
return false;
return handleSuccess(originalPath, hits, matchLength, "when matching project files"); return handleSuccess(originalPath, hits, matchLength, "when matching project files");
} }
CacheEntry foundPath = findInSearchPaths(originalPath, fileHandler, directoryHandler); bool FileInProjectFinder::checkSearchPaths(const FilePath &originalPath,
if (!foundPath.paths.isEmpty()) { FileHandler fileHandler,
return handleSuccess(originalPath, foundPath.paths, foundPath.matchLength, DirectoryHandler directoryHandler) const
{
for (const FilePath &dirPath : m_searchDirectories) {
const CacheEntry found = findInSearchPath(dirPath, originalPath,
fileHandler, directoryHandler);
if (!found.paths.isEmpty()) {
return handleSuccess(originalPath, found.paths, found.matchLength,
"in search path"); "in search path");
} }
qCDebug(finderLog) << "FileInProjectFinder: checking absolute path in sysroot ...";
// check if absolute path is found in sysroot
if (!m_sysroot.isEmpty()) {
const FilePath sysrootPath = m_sysroot.pathAppended(originalPath.toUrlishString());
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");
} }
return CacheEntry(); bool FileInProjectFinder::findFileOrDirectory(const FilePath &originalPath,
FileHandler fileHandler,
DirectoryHandler directoryHandler) const
{
if (originalPath.isEmpty()) {
qCDebug(finderLog) << "FileInProjectFinder: malformed original path, returning";
return false;
}
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;