diff options
author | Christian Kandeler <christian.kandeler@qt.io> | 2019-02-25 17:45:39 +0100 |
---|---|---|
committer | Christian Kandeler <christian.kandeler@qt.io> | 2019-03-04 09:52:13 +0000 |
commit | d0db212575909ca18244bdfc99b952dffcd0e902 (patch) | |
tree | f0fbfb40bc34b20a5f4539b6936c8854e78433ee /src/libs/utils/fileinprojectfinder.cpp | |
parent | b0e125ac11b1ad80717366a1270412d6b6b1be41 (diff) |
Resolve ambiguous results of searches for relative file paths
Qt Creator extracts file names from the output of external processes in
various places, for instance from compilers during the build. It often
happens that such file names are not absolute, so they cannot be
directly opened in an editor when the user clicks on their
representation in some widget. That's why they are processed by the
FileInProjectFinder facility first.
This patch enhances the FileInProjectFinder to be able to return more
than one candidate for a relative file path, and allows the user to
choose between these candidates where possible. This way, we won't open
a random file anymore when a project contains more than one file with
the same name.
Fixes: QTCREATORBUG-13623
Change-Id: Id19c9eace3e6b3dbde89f6528e6d02b55872d747
Reviewed-by: hjk <hjk@qt.io>
Diffstat (limited to 'src/libs/utils/fileinprojectfinder.cpp')
-rw-r--r-- | src/libs/utils/fileinprojectfinder.cpp | 137 |
1 files changed, 87 insertions, 50 deletions
diff --git a/src/libs/utils/fileinprojectfinder.cpp b/src/libs/utils/fileinprojectfinder.cpp index 04495893920..054e5d9c3b4 100644 --- a/src/libs/utils/fileinprojectfinder.cpp +++ b/src/libs/utils/fileinprojectfinder.cpp @@ -31,11 +31,13 @@ #include "qrcparser.h" #include "qtcassert.h" +#include <QCursor> #include <QDebug> +#include <QDir> #include <QFileInfo> #include <QLoggingCategory> +#include <QMenu> #include <QUrl> -#include <QDir> #include <algorithm> @@ -140,12 +142,12 @@ void FileInProjectFinder::addMappedPath(const FileName &localFilePath, const QSt folder specified. Third, we walk the list of project files, and search for a file name match there. If all fails, it returns the original path from the file URL. */ -QString FileInProjectFinder::findFile(const QUrl &fileUrl, bool *success) const +FileNameList FileInProjectFinder::findFile(const QUrl &fileUrl, bool *success) const { qCDebug(finderLog) << "FileInProjectFinder: trying to find file" << fileUrl.toString() << "..."; if (fileUrl.scheme() == "qrc" || fileUrl.toString().startsWith(':')) { - const QString result = m_qrcUrlFinder.find(fileUrl); + const FileNameList result = m_qrcUrlFinder.find(fileUrl); if (!result.isEmpty()) { if (success) *success = true; @@ -157,10 +159,12 @@ QString FileInProjectFinder::findFile(const QUrl &fileUrl, bool *success) const if (originalPath.isEmpty()) // e.g. qrc:// originalPath = fileUrl.path(); - QString result = originalPath; + FileNameList result; bool found = findFileOrDirectory(originalPath, [&](const QString &fileName, int) { - result = fileName; + result << FileName::fromString(fileName); }); + if (!found) + result << FileName::fromString(originalPath); if (success) *success = found; @@ -168,12 +172,12 @@ QString FileInProjectFinder::findFile(const QUrl &fileUrl, bool *success) const return result; } -bool FileInProjectFinder::handleSuccess(const QString &originalPath, const QString &found, +bool FileInProjectFinder::handleSuccess(const QString &originalPath, const QStringList &found, int matchLength, const char *where) const { qCDebug(finderLog) << "FileInProjectFinder: found" << found << where; CacheEntry entry; - entry.path = found; + entry.paths = found; entry.matchLength = matchLength; m_cache.insert(originalPath, entry); return true; @@ -202,8 +206,10 @@ bool FileInProjectFinder::findFileOrDirectory(const QString &originalPath, FileH if (node) { if (!node->localPath.isEmpty()) { const QString localPath = node->localPath.toString(); - if (checkPath(localPath, origLength, fileHandler, directoryHandler)) - return handleSuccess(originalPath, localPath, origLength, "in mapped paths"); + if (checkPath(localPath, origLength, fileHandler, directoryHandler)) { + return handleSuccess(originalPath, QStringList(localPath), origLength, + "in mapped paths"); + } } else if (directoryHandler) { directoryHandler(node->children.keys(), origLength); qCDebug(finderLog) << "FileInProjectFinder: found virtual directory" << originalPath @@ -216,13 +222,18 @@ bool FileInProjectFinder::findFileOrDirectory(const QString &originalPath, FileH if (it != m_cache.end()) { qCDebug(finderLog) << "FileInProjectFinder: checking cache ..."; // check if cached path is still there - const CacheEntry &candidate = it.value(); - if (checkPath(candidate.path, candidate.matchLength, fileHandler, directoryHandler)) { - qCDebug(finderLog) << "FileInProjectFinder: found" << candidate.path << "in the cache"; - return true; - } else { - m_cache.erase(it); + 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()) { @@ -244,7 +255,7 @@ bool FileInProjectFinder::findFileOrDirectory(const QString &originalPath, FileH if (prefixToIgnore == -1 && checkPath(originalPath, origLength, fileHandler, directoryHandler)) { - return handleSuccess(originalPath, originalPath, origLength, + return handleSuccess(originalPath, QStringList(originalPath), origLength, "in project directory"); } } @@ -267,8 +278,11 @@ bool FileInProjectFinder::findFileOrDirectory(const QString &originalPath, FileH candidate.remove(0, prefixToIgnore); candidate.prepend(m_projectDir.toString()); const int matchLength = origLength - prefixToIgnore; - if (checkPath(candidate, matchLength, fileHandler, directoryHandler)) - return handleSuccess(originalPath, candidate, matchLength, "in project directory"); + // FIXME: This might be a worse match than what we find later. + if (checkPath(candidate, matchLength, fileHandler, directoryHandler)) { + return handleSuccess(originalPath, QStringList(candidate), matchLength, + "in project directory"); + } prefixToIgnore = originalPath.indexOf(separator, prefixToIgnore + 1); } } @@ -282,17 +296,23 @@ bool FileInProjectFinder::findFileOrDirectory(const QString &originalPath, FileH matches.append(filesWithSameFileName(lastSegment)); if (directoryHandler) matches.append(pathSegmentsWithSameName(lastSegment)); - const QString matchedFilePath = bestMatch(matches, originalPath); - const int matchLength = commonPostFixLength(matchedFilePath, originalPath); - if (!matchedFilePath.isEmpty() - && checkPath(matchedFilePath, matchLength, fileHandler, directoryHandler)) { - return handleSuccess(originalPath, matchedFilePath, matchLength, - "when matching project files"); + const QStringList matchedFilePaths = bestMatches(matches, originalPath); + if (!matchedFilePaths.empty()) { + const int matchLength = commonPostFixLength(matchedFilePaths.first(), originalPath); + QStringList hits; + for (const QString &matchedFilePath : matchedFilePaths) { + if (checkPath(matchedFilePath, matchLength, fileHandler, directoryHandler)) + hits << matchedFilePath; + } + if (!hits.empty()) + return handleSuccess(originalPath, hits, matchLength, "when matching project files"); } CacheEntry foundPath = findInSearchPaths(originalPath, fileHandler, directoryHandler); - if (!foundPath.path.isEmpty()) - return handleSuccess(originalPath, foundPath.path, foundPath.matchLength, "in search path"); + if (!foundPath.paths.isEmpty()) { + return handleSuccess(originalPath, foundPath.paths, foundPath.matchLength, + "in search path"); + } qCDebug(finderLog) << "FileInProjectFinder: checking absolute path in sysroot ..."; @@ -300,8 +320,10 @@ bool FileInProjectFinder::findFileOrDirectory(const QString &originalPath, FileH if (!m_sysroot.isEmpty()) { FileName sysrootPath = m_sysroot; sysrootPath.appendPath(originalPath); - if (checkPath(sysrootPath.toString(), origLength, fileHandler, directoryHandler)) - return handleSuccess(originalPath, sysrootPath.toString(), origLength, "in sysroot"); + if (checkPath(sysrootPath.toString(), origLength, fileHandler, directoryHandler)) { + return handleSuccess(originalPath, QStringList(sysrootPath.toString()), origLength, + "in sysroot"); + } } qCDebug(finderLog) << "FileInProjectFinder: couldn't find file!"; @@ -315,7 +337,7 @@ FileInProjectFinder::CacheEntry FileInProjectFinder::findInSearchPaths( for (const FileName &dirPath : m_searchDirectories) { const CacheEntry found = findInSearchPath(dirPath.toString(), filePath, fileHandler, directoryHandler); - if (!found.path.isEmpty()) + if (!found.paths.isEmpty()) return found; } @@ -340,17 +362,17 @@ FileInProjectFinder::CacheEntry FileInProjectFinder::findInSearchPath( QString s = filePath; while (!s.isEmpty()) { CacheEntry result; - result.path = searchPath + QLatin1Char('/') + s; + result.paths << searchPath + '/' + s; result.matchLength = s.length() + 1; - qCDebug(finderLog) << "FileInProjectFinder: trying" << result.path; + qCDebug(finderLog) << "FileInProjectFinder: trying" << result.paths.first(); - if (checkPath(result.path, result.matchLength, fileHandler, directoryHandler)) + if (checkPath(result.paths.first(), result.matchLength, fileHandler, directoryHandler)) return result; QString next = chopFirstDir(s); if (next.isEmpty()) { if (directoryHandler && QFileInfo(searchPath).fileName() == s) { - result.path = searchPath; + result.paths = QStringList{searchPath}; directoryHandler(QDir(searchPath).entryList(), result.matchLength); return result; } @@ -399,24 +421,30 @@ int FileInProjectFinder::commonPostFixLength(const QString &candidatePath, return rank; } -QString FileInProjectFinder::bestMatch(const QStringList &filePaths, const QString &filePathToFind) +QStringList FileInProjectFinder::bestMatches(const QStringList &filePaths, + const QString &filePathToFind) { if (filePaths.isEmpty()) - return QString(); + return {}; if (filePaths.length() == 1) { qCDebug(finderLog) << "FileInProjectFinder: found" << filePaths.first() << "in project files"; - return filePaths.first(); + return filePaths; } - auto it = std::max_element(filePaths.constBegin(), filePaths.constEnd(), - [&filePathToFind] (const QString &a, const QString &b) -> bool { - return commonPostFixLength(a, filePathToFind) < commonPostFixLength(b, filePathToFind); - }); - if (it != filePaths.cend()) { - qCDebug(finderLog) << "FileInProjectFinder: found best match" << *it << "in project files"; - return *it; + int bestRank = -1; + QStringList bestFilePaths; + for (const QString &fp : filePaths) { + const int currentRank = commonPostFixLength(fp, filePathToFind); + if (currentRank < bestRank) + continue; + if (currentRank > bestRank) { + bestRank = currentRank; + bestFilePaths.clear(); + } + bestFilePaths << fp; } - return QString(); + QTC_CHECK(!bestFilePaths.empty()); + return bestFilePaths; } FileNameList FileInProjectFinder::searchDirectories() const @@ -434,9 +462,9 @@ FileInProjectFinder::PathMappingNode::~PathMappingNode() qDeleteAll(children); } -QString FileInProjectFinder::QrcUrlFinder::find(const QUrl &fileUrl) const +FileNameList FileInProjectFinder::QrcUrlFinder::find(const QUrl &fileUrl) const { - QString result; + FileNameList result; const auto fileIt = m_fileCache.constFind(fileUrl); if (fileIt != m_fileCache.cend()) return fileIt.value(); @@ -448,10 +476,7 @@ QString FileInProjectFinder::QrcUrlFinder::find(const QUrl &fileUrl) const continue; QStringList hits; qrcParser->collectFilesAtPath(QrcParser::normalizedQrcFilePath(fileUrl.toString()), &hits); - if (!hits.empty()) { - result = hits.first(); - break; - } + result = transform(hits, [](const QString &fp) { return FileName::fromString(fp); }); } m_fileCache.insert(fileUrl, result); return result; @@ -464,4 +489,16 @@ void FileInProjectFinder::QrcUrlFinder::setProjectFiles(const FileNameList &proj m_parserCache.clear(); } +FileName chooseFileFromList(const FileNameList &candidates) +{ + if (candidates.length() == 1) + return candidates.first(); + QMenu filesMenu; + for (const FileName &candidate : candidates) + filesMenu.addAction(candidate.toUserOutput()); + if (const QAction * const action = filesMenu.exec(QCursor::pos())) + return FileName::fromUserInput(action->text()); + return FileName(); +} + } // namespace Utils |