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 <QUrl>
#include <algorithm>
namespace {
static Q_LOGGING_CATEGORY(finderLog, "qtc.utils.fileinprojectfinder", QtWarningMsg);
}
@@ -161,24 +159,32 @@ bool FileInProjectFinder::handleSuccess(const FilePath &originalPath, const File
return true;
}
bool FileInProjectFinder::findFileOrDirectory(const FilePath &originalPath, FileHandler fileHandler,
bool FileInProjectFinder::checkRootDirectory(const FilePath &originalPath,
DirectoryHandler directoryHandler) const
{
if (originalPath.isEmpty()) {
qCDebug(finderLog) << "FileInProjectFinder: malformed original path, returning";
if (!directoryHandler)
return false;
}
if (directoryHandler && originalPath == m_projectDir && originalPath.isDir()) {
const QStringList realEntries = QDir(originalPath.toFSPathString())
if (originalPath != m_projectDir || !originalPath.isDir())
return false;
const QString originalFSPath = originalPath.toFSPathString();
const QStringList realEntries = QDir(originalFSPath)
.entryList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
directoryHandler(realEntries, originalPath.toFSPathString().size());
directoryHandler(realEntries, originalFSPath.size());
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;
for (const auto &segment : segments) {
for (const QString &segment : segments) {
auto it = node->children.find(segment);
if (it == node->children.end()) {
node = nullptr;
@@ -186,13 +192,14 @@ bool FileInProjectFinder::findFileOrDirectory(const FilePath &originalPath, File
}
node = *it;
}
if (!node)
return false;
const int origLength = origFsPath.size();
const int origLength = originalPath.toFSPathString().length();
if (node) {
if (!node->localPath.isEmpty()) {
if (checkPath(node->localPath, origLength, fileHandler, directoryHandler)) {
return handleSuccess(originalPath, {node->localPath}, origLength,
"in mapped paths");
return handleSuccess(originalPath, {node->localPath}, origLength, "in mapped paths");
}
} else if (directoryHandler) {
directoryHandler(node->children.keys(), origLength);
@@ -200,12 +207,18 @@ bool FileInProjectFinder::findFileOrDirectory(const FilePath &originalPath, File
<< "in mapped paths";
return true;
}
return false;
}
bool FileInProjectFinder::checkCache(const FilePath &originalPath,
FileHandler fileHandler,
DirectoryHandler directoryHandler) const
{
auto it = m_cache.find(originalPath);
if (it != m_cache.end()) {
if (it == m_cache.end())
return false;
qCDebug(finderLog) << "FileInProjectFinder: checking cache ...";
// 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)) {
@@ -215,16 +228,28 @@ bool FileInProjectFinder::findFileOrDirectory(const FilePath &originalPath, File
pathIt = candidate.paths.erase(pathIt);
}
}
if (!candidate.paths.empty())
return true;
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 ...";
int prefixToIgnore = -1;
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
@@ -233,99 +258,138 @@ bool FileInProjectFinder::findFileOrDirectory(const FilePath &originalPath, File
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();
prefixToIgnore = origFsPath.indexOf(appResourcePath) + appResourcePath.length();
}
}
if (prefixToIgnore == -1
&& checkPath(originalPath, origLength, fileHandler, directoryHandler)) {
return handleSuccess(originalPath, {originalPath}, origLength,
"in project directory");
return handleSuccess(originalPath, {originalPath}, origLength, "in project directory");
}
}
qCDebug(finderLog) << "FileInProjectFinder:"
<< "checking stripped paths 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)) {
if (!originalPath.isAbsolutePath() && !originalPath.startsWith(separator)) {
prefixToIgnore = 0;
} else {
prefixToIgnore = originalPath.toFSPathString().indexOf(separator);
prefixToIgnore = origFsPath.indexOf(separator);
}
}
// Repeatedly strip directories from the front
while (prefixToIgnore != -1) {
QString candidateString = originalPath.toFSPathString();
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");
return handleSuccess(originalPath, {candidate}, matchLength, "in project directory");
}
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 ...";
QStringList matches;
const QString lastSegment = originalPath.fileName();
QStringList matches;
if (fileHandler)
matches.append(filesWithSameFileName(lastSegment));
if (directoryHandler)
matches.append(pathSegmentsWithSameName(lastSegment));
const QStringList matchedFilePaths = bestMatches(matches, originalPath.toUrlishString());
if (!matchedFilePaths.empty()) {
if (matchedFilePaths.isEmpty())
return false;
const int matchLength = commonPostFixLength(matchedFilePaths.first(), originalPath.toUrlishString());
FilePaths hits;
for (const QString &matchedFilePath : matchedFilePaths) {
if (checkPath(FilePath::fromString(matchedFilePath), matchLength, fileHandler, directoryHandler))
hits.append(FilePath::fromString(matchedFilePath));
}
if (!hits.empty())
if (hits.isEmpty())
return false;
return handleSuccess(originalPath, hits, matchLength, "when matching project files");
}
CacheEntry foundPath = findInSearchPaths(originalPath, fileHandler, directoryHandler);
if (!foundPath.paths.isEmpty()) {
return handleSuccess(originalPath, foundPath.paths, foundPath.matchLength,
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);
if (!found.paths.isEmpty()) {
return handleSuccess(originalPath, found.paths, found.matchLength,
"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;
}
FileInProjectFinder::CacheEntry FileInProjectFinder::findInSearchPaths(
const FilePath &filePath, FileHandler fileHandler, DirectoryHandler directoryHandler) const
bool FileInProjectFinder::checkSysroot(const FilePath &originalPath,
FileHandler fileHandler,
DirectoryHandler directoryHandler) const
{
for (const FilePath &dirPath : m_searchDirectories) {
const CacheEntry found = findInSearchPath(dirPath, filePath,
fileHandler, directoryHandler);
if (!found.paths.isEmpty())
return found;
qCDebug(finderLog) << "FileInProjectFinder: checking absolute path in sysroot ...";
if (m_sysroot.isEmpty())
return false;
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)

View File

@@ -62,13 +62,30 @@ private:
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,
FileHandler fileHandler, DirectoryHandler directoryHandler);
QStringList filesWithSameFileName(const QString &fileName) 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,
const char *where) const;