FileInProjectFinder: Report length of matched postfix

We want to know how exact the match is, so that we can reject results
that don't fit our needs. We cannot pass an extra fuzzy/exact parameter
to findFileOrDirectory, because we cache the results, and we need to be
able to reuse results obtained from different searches.

Task-number: QTCREATORBUG-21107
Change-Id: I870100f4c0acd0e9b007f076543340b34a25a447
Reviewed-by: hjk <hjk@qt.io>
Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
This commit is contained in:
Ulf Hermann
2018-09-19 12:25:14 +02:00
committed by Tim Jenssen
parent d38064892b
commit c0378ee7e8
2 changed files with 76 additions and 51 deletions

View File

@@ -42,15 +42,16 @@ Q_LOGGING_CATEGORY(finderLog, "qtc.utils.fileinprojectfinder");
namespace Utils { namespace Utils {
static bool checkPath(const QString &candidate, FileInProjectFinder::FileHandler fileHandler, static bool checkPath(const QString &candidate, int matchLength,
FileInProjectFinder::FileHandler fileHandler,
FileInProjectFinder::DirectoryHandler directoryHandler) FileInProjectFinder::DirectoryHandler directoryHandler)
{ {
const QFileInfo candidateInfo(candidate); const QFileInfo candidateInfo(candidate);
if (fileHandler && candidateInfo.isFile()) { if (fileHandler && candidateInfo.isFile()) {
fileHandler(candidate); fileHandler(candidate, matchLength);
return true; return true;
} else if (directoryHandler && candidateInfo.isDir()) { } else if (directoryHandler && candidateInfo.isDir()) {
directoryHandler(QDir(candidate).entryList()); directoryHandler(QDir(candidate).entryList(), matchLength);
return true; return true;
} }
return false; return false;
@@ -144,7 +145,7 @@ QString FileInProjectFinder::findFile(const QUrl &fileUrl, bool *success) const
originalPath = fileUrl.path(); originalPath = fileUrl.path();
QString result; QString result;
bool found = findFileOrDirectory(originalPath, [&](const QString &fileName) { bool found = findFileOrDirectory(originalPath, [&](const QString &fileName, int) {
result = fileName; result = fileName;
}); });
@@ -155,10 +156,13 @@ QString FileInProjectFinder::findFile(const QUrl &fileUrl, bool *success) const
} }
bool FileInProjectFinder::handleSuccess(const QString &originalPath, const QString &found, bool FileInProjectFinder::handleSuccess(const QString &originalPath, const QString &found,
const char *where) const int matchLength, const char *where) const
{ {
qCDebug(finderLog) << "FileInProjectFinder: found" << found << where; qCDebug(finderLog) << "FileInProjectFinder: found" << found << where;
m_cache.insert(originalPath, found); CacheEntry entry;
entry.path = found;
entry.matchLength = matchLength;
m_cache.insert(originalPath, entry);
return true; return true;
} }
@@ -181,15 +185,16 @@ bool FileInProjectFinder::findFileOrDirectory(const QString &originalPath, FileH
node = *it; node = *it;
} }
const int origLength = originalPath.length();
if (node) { if (node) {
if (!node->localPath.isEmpty()) { if (!node->localPath.isEmpty()) {
const QString localPath = node->localPath.toString(); const QString localPath = node->localPath.toString();
if (checkPath(localPath, fileHandler, directoryHandler)) if (checkPath(localPath, origLength, fileHandler, directoryHandler))
return handleSuccess(originalPath, localPath, "in deployment data"); return handleSuccess(originalPath, localPath, origLength, "in mapped paths");
} else if (directoryHandler) { } else if (directoryHandler) {
directoryHandler(node->children.keys()); directoryHandler(node->children.keys(), origLength);
qCDebug(finderLog) << "FileInProjectFinder: found virtual directory" << originalPath qCDebug(finderLog) << "FileInProjectFinder: found virtual directory" << originalPath
<< "in deployment data"; << "in mapped paths";
return true; return true;
} }
} }
@@ -198,9 +203,9 @@ bool FileInProjectFinder::findFileOrDirectory(const QString &originalPath, FileH
if (it != m_cache.end()) { if (it != m_cache.end()) {
qCDebug(finderLog) << "FileInProjectFinder: checking cache ..."; qCDebug(finderLog) << "FileInProjectFinder: checking cache ...";
// check if cached path is still there // check if cached path is still there
const QString &candidate = it.value(); const CacheEntry &candidate = it.value();
if (checkPath(candidate, fileHandler, directoryHandler)) { if (checkPath(candidate.path, candidate.matchLength, fileHandler, directoryHandler)) {
qCDebug(finderLog) << "FileInProjectFinder: found" << candidate << "in the cache"; qCDebug(finderLog) << "FileInProjectFinder: found" << candidate.path << "in the cache";
return true; return true;
} else { } else {
m_cache.erase(it); m_cache.erase(it);
@@ -223,8 +228,12 @@ bool FileInProjectFinder::findFileOrDirectory(const QString &originalPath, FileH
prefixToIgnore = originalPath.indexOf(appResourcePath) + appResourcePath.length(); prefixToIgnore = originalPath.indexOf(appResourcePath) + appResourcePath.length();
} }
} }
if (prefixToIgnore == -1 && checkPath(originalPath, fileHandler, directoryHandler))
return handleSuccess(originalPath, originalPath, "in project directory"); if (prefixToIgnore == -1
&& checkPath(originalPath, origLength, fileHandler, directoryHandler)) {
return handleSuccess(originalPath, originalPath, origLength,
"in project directory");
}
} }
qCDebug(finderLog) << "FileInProjectFinder:" qCDebug(finderLog) << "FileInProjectFinder:"
@@ -244,8 +253,9 @@ bool FileInProjectFinder::findFileOrDirectory(const QString &originalPath, FileH
QString candidate = originalPath; QString candidate = originalPath;
candidate.remove(0, prefixToIgnore); candidate.remove(0, prefixToIgnore);
candidate.prepend(m_projectDir.toString()); candidate.prepend(m_projectDir.toString());
if (checkPath(candidate, fileHandler, directoryHandler)) const int matchLength = origLength - prefixToIgnore;
return handleSuccess(originalPath, candidate, "in project directory"); if (checkPath(candidate, matchLength, fileHandler, directoryHandler))
return handleSuccess(originalPath, candidate, matchLength, "in project directory");
prefixToIgnore = originalPath.indexOf(separator, prefixToIgnore + 1); prefixToIgnore = originalPath.indexOf(separator, prefixToIgnore + 1);
} }
} }
@@ -260,12 +270,16 @@ bool FileInProjectFinder::findFileOrDirectory(const QString &originalPath, FileH
if (directoryHandler) if (directoryHandler)
matches.append(pathSegmentsWithSameName(lastSegment)); matches.append(pathSegmentsWithSameName(lastSegment));
const QString matchedFilePath = bestMatch(matches, originalPath); const QString matchedFilePath = bestMatch(matches, originalPath);
if (!matchedFilePath.isEmpty() && checkPath(matchedFilePath, fileHandler, directoryHandler)) const int matchLength = commonPostFixLength(matchedFilePath, originalPath);
return handleSuccess(originalPath, matchedFilePath, "when matching project files"); if (!matchedFilePath.isEmpty()
&& checkPath(matchedFilePath, matchLength, fileHandler, directoryHandler)) {
return handleSuccess(originalPath, matchedFilePath, matchLength,
"when matching project files");
}
QString foundPath = findInSearchPaths(originalPath, fileHandler, directoryHandler); CacheEntry foundPath = findInSearchPaths(originalPath, fileHandler, directoryHandler);
if (!foundPath.isEmpty()) if (!foundPath.path.isEmpty())
return handleSuccess(originalPath, foundPath, "in search path"); return handleSuccess(originalPath, foundPath.path, foundPath.matchLength, "in search path");
qCDebug(finderLog) << "FileInProjectFinder: checking absolute path in sysroot ..."; qCDebug(finderLog) << "FileInProjectFinder: checking absolute path in sysroot ...";
@@ -273,8 +287,8 @@ bool FileInProjectFinder::findFileOrDirectory(const QString &originalPath, FileH
if (!m_sysroot.isEmpty()) { if (!m_sysroot.isEmpty()) {
FileName sysrootPath = m_sysroot; FileName sysrootPath = m_sysroot;
sysrootPath.appendPath(originalPath); sysrootPath.appendPath(originalPath);
if (checkPath(sysrootPath.toString(), fileHandler, directoryHandler)) if (checkPath(sysrootPath.toString(), origLength, fileHandler, directoryHandler))
return handleSuccess(originalPath, sysrootPath.toString(), "in sysroot"); return handleSuccess(originalPath, sysrootPath.toString(), origLength, "in sysroot");
} }
qCDebug(finderLog) << "FileInProjectFinder: couldn't find file!"; qCDebug(finderLog) << "FileInProjectFinder: couldn't find file!";
@@ -282,16 +296,17 @@ bool FileInProjectFinder::findFileOrDirectory(const QString &originalPath, FileH
return false; return false;
} }
QString FileInProjectFinder::findInSearchPaths(const QString &filePath, FileHandler fileHandler, FileInProjectFinder::CacheEntry FileInProjectFinder::findInSearchPaths(
DirectoryHandler directoryHandler) const const QString &filePath, FileHandler fileHandler, DirectoryHandler directoryHandler) const
{ {
for (const FileName &dirPath : m_searchDirectories) { for (const FileName &dirPath : m_searchDirectories) {
const QString found = findInSearchPath(dirPath.toString(), filePath, fileHandler, const CacheEntry found = findInSearchPath(dirPath.toString(), filePath,
directoryHandler); fileHandler, directoryHandler);
if (!found.isEmpty()) if (!found.path.isEmpty())
return found; return found;
} }
return QString();
return CacheEntry();
} }
static QString chopFirstDir(const QString &dirPath) static QString chopFirstDir(const QString &dirPath)
@@ -303,32 +318,35 @@ static QString chopFirstDir(const QString &dirPath)
return dirPath.mid(i + 1); return dirPath.mid(i + 1);
} }
QString FileInProjectFinder::findInSearchPath(const QString &searchPath, const QString &filePath, FileInProjectFinder::CacheEntry FileInProjectFinder::findInSearchPath(
FileHandler fileHandler, const QString &searchPath, const QString &filePath,
DirectoryHandler directoryHandler) FileHandler fileHandler, DirectoryHandler directoryHandler)
{ {
qCDebug(finderLog) << "FileInProjectFinder: checking search path" << searchPath; qCDebug(finderLog) << "FileInProjectFinder: checking search path" << searchPath;
QString s = filePath; QString s = filePath;
while (!s.isEmpty()) { while (!s.isEmpty()) {
const QString candidate = searchPath + QLatin1Char('/') + s; CacheEntry result;
qCDebug(finderLog) << "FileInProjectFinder: trying" << candidate; result.path = searchPath + QLatin1Char('/') + s;
result.matchLength = s.length() + 1;
qCDebug(finderLog) << "FileInProjectFinder: trying" << result.path;
if (checkPath(candidate, fileHandler, directoryHandler)) if (checkPath(result.path, result.matchLength, fileHandler, directoryHandler))
return candidate; return result;
QString next = chopFirstDir(s); QString next = chopFirstDir(s);
if (next.isEmpty()) { if (next.isEmpty()) {
if (directoryHandler && QFileInfo(searchPath).fileName() == s) { if (directoryHandler && QFileInfo(searchPath).fileName() == s) {
directoryHandler(QDir(searchPath).entryList()); result.path = searchPath;
return searchPath; directoryHandler(QDir(searchPath).entryList(), result.matchLength);
return result;
} }
break; break;
} }
s = next; s = next;
} }
return QString(); return CacheEntry();
} }
QStringList FileInProjectFinder::filesWithSameFileName(const QString &fileName) const QStringList FileInProjectFinder::filesWithSameFileName(const QString &fileName) const
@@ -358,7 +376,8 @@ QStringList FileInProjectFinder::pathSegmentsWithSameName(const QString &pathSeg
return result; return result;
} }
int FileInProjectFinder::rankFilePath(const QString &candidatePath, const QString &filePathToFind) int FileInProjectFinder::commonPostFixLength(const QString &candidatePath,
const QString &filePathToFind)
{ {
int rank = 0; int rank = 0;
for (int a = candidatePath.length(), b = filePathToFind.length(); for (int a = candidatePath.length(), b = filePathToFind.length();
@@ -378,7 +397,7 @@ QString FileInProjectFinder::bestMatch(const QStringList &filePaths, const QStri
} }
auto it = std::max_element(filePaths.constBegin(), filePaths.constEnd(), auto it = std::max_element(filePaths.constBegin(), filePaths.constEnd(),
[&filePathToFind] (const QString &a, const QString &b) -> bool { [&filePathToFind] (const QString &a, const QString &b) -> bool {
return rankFilePath(a, filePathToFind) < rankFilePath(b, filePathToFind); return commonPostFixLength(a, filePathToFind) < commonPostFixLength(b, filePathToFind);
}); });
if (it != filePaths.cend()) { if (it != filePaths.cend()) {
qCDebug(finderLog) << "FileInProjectFinder: found best match" << *it << "in project files"; qCDebug(finderLog) << "FileInProjectFinder: found best match" << *it << "in project files";

View File

@@ -39,8 +39,8 @@ class QTCREATOR_UTILS_EXPORT FileInProjectFinder
{ {
public: public:
using FileHandler = std::function<void(const QString &)>; using FileHandler = std::function<void(const QString &, int)>;
using DirectoryHandler = std::function<void(const QStringList &)>; using DirectoryHandler = std::function<void(const QStringList &, int)>;
FileInProjectFinder(); FileInProjectFinder();
~FileInProjectFinder(); ~FileInProjectFinder();
@@ -68,16 +68,22 @@ private:
QHash<QString, PathMappingNode *> children; QHash<QString, PathMappingNode *> children;
}; };
QString findInSearchPaths(const QString &filePath, FileHandler fileHandler, struct CacheEntry {
QString path;
int matchLength = 0;
};
CacheEntry findInSearchPaths(const QString &filePath, FileHandler fileHandler,
DirectoryHandler directoryHandler) const; DirectoryHandler directoryHandler) const;
static QString findInSearchPath(const QString &searchPath, const QString &filePath, static CacheEntry findInSearchPath(const QString &searchPath, const QString &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 handleSuccess(const QString &originalPath, const QString &found, const char *where) const; bool handleSuccess(const QString &originalPath, const QString &found, int confidence,
const char *where) const;
static int rankFilePath(const QString &candidatePath, const QString &filePathToFind); static int commonPostFixLength(const QString &candidatePath, const QString &filePathToFind);
static QString bestMatch(const QStringList &filePaths, const QString &filePathToFind); static QString bestMatch(const QStringList &filePaths, const QString &filePathToFind);
FileName m_projectDir; FileName m_projectDir;
@@ -86,7 +92,7 @@ private:
FileNameList m_searchDirectories; FileNameList m_searchDirectories;
PathMappingNode m_pathMapRoot; PathMappingNode m_pathMapRoot;
mutable QHash<QString,QString> m_cache; mutable QHash<QString, CacheEntry> m_cache;
}; };
} // namespace Utils } // namespace Utils