diff --git a/src/libs/qmljs/qmljsmodelmanagerinterface.cpp b/src/libs/qmljs/qmljsmodelmanagerinterface.cpp index 5f649931ad3..444d429a4bc 100644 --- a/src/libs/qmljs/qmljsmodelmanagerinterface.cpp +++ b/src/libs/qmljs/qmljsmodelmanagerinterface.cpp @@ -446,6 +446,20 @@ bool pInfoLessThanImports(const ModelManagerInterface::ProjectInfo &p1, } +static QList generatedQrc(QStringList applicationDirectories) +{ + QList res; + for (const QString &pathStr : applicationDirectories) { + Utils::FilePath path = Utils::FilePath::fromString(pathStr); + Utils::FilePath generatedQrcDir = path.pathAppended(".rcc"); + if (generatedQrcDir.isReadableDir()) { + for (const Utils::FilePath & qrcPath: generatedQrcDir.dirEntries(FileFilter(QStringList({QStringLiteral(u"*.qrc")}), QDir::Files))) + res.append(qrcPath.canonicalPath()); + } + } + return res; +} + void ModelManagerInterface::iterateQrcFiles( ProjectExplorer::Project *project, QrcResourceSelector resources, const std::function &callback) @@ -461,18 +475,21 @@ void ModelManagerInterface::iterateQrcFiles( Utils::sort(pInfos, &pInfoLessThanAll); } - QSet pathsChecked; + QSet pathsChecked; for (const ModelManagerInterface::ProjectInfo &pInfo : qAsConst(pInfos)) { QStringList qrcFilePaths; if (resources == ActiveQrcResources) qrcFilePaths = pInfo.activeResourceFiles; else qrcFilePaths = pInfo.allResourceFiles; - for (const QString &qrcFilePath : qAsConst(qrcFilePaths)) { + for (const Utils::FilePath &p : generatedQrc(pInfo.applicationDirectories)) + qrcFilePaths.append(p.toString()); + for (const QString &qrcFilePathStr : qAsConst(qrcFilePaths)) { + auto qrcFilePath = Utils::FilePath::fromString(qrcFilePathStr); if (pathsChecked.contains(qrcFilePath)) continue; pathsChecked.insert(qrcFilePath); - QrcParser::ConstPtr qrcFile = m_qrcCache.parsedPath(qrcFilePath); + QrcParser::ConstPtr qrcFile = m_qrcCache.parsedPath(qrcFilePath.toString()); if (qrcFile.isNull()) continue; callback(qrcFile); @@ -589,6 +606,8 @@ void ModelManagerInterface::updateProjectInfo(const ProjectInfo &pinfo, ProjectE m_qrcContents = pinfo.resourceFileContents; for (const QString &newQrc : qAsConst(pinfo.allResourceFiles)) m_qrcCache.addPath(newQrc, m_qrcContents.value(newQrc)); + for (const Utils::FilePath &newQrc : generatedQrc(pinfo.applicationDirectories)) + m_qrcCache.addPath(newQrc.toString(), m_qrcContents.value(newQrc.toString())); for (const QString &oldQrc : qAsConst(oldInfo.allResourceFiles)) m_qrcCache.removePath(oldQrc); @@ -1180,6 +1199,29 @@ void ModelManagerInterface::maybeScan(const PathsAndLanguages &importPaths) } } +static QList minimalPrefixPaths(const QStringList &paths) +{ + QList sortedPaths; + // find minimal prefix, ensure '/' at end + for (const QString &pathStr : qAsConst(paths)) { + Utils::FilePath path = Utils::FilePath::fromString(pathStr); + if (!path.endsWith("/")) + path.setPath(path.path() + "/"); + if (path.path().length() > 1) + sortedPaths.append(path); + } + std::sort(sortedPaths.begin(), sortedPaths.end()); + QList res; + QString lastPrefix; + for (auto it = sortedPaths.begin(); it != sortedPaths.end(); ++it) { + if (lastPrefix.isEmpty() || !it->startsWith(lastPrefix)) { + lastPrefix = it->path(); + res.append(*it); + } + } + return res; +} + void ModelManagerInterface::updateImportPaths() { if (m_indexerDisabled) @@ -1243,6 +1285,7 @@ void ModelManagerInterface::updateImportPaths() m_allImportPaths = allImportPaths; m_activeBundles = activeBundles; m_extendedBundles = extendedBundles; + m_applicationPaths = minimalPrefixPaths(allApplicationDirectories); } @@ -1253,10 +1296,13 @@ void ModelManagerInterface::updateImportPaths() QSet newLibraries; for (const Document::Ptr &doc : qAsConst(snapshot)) findNewLibraryImports(doc, snapshot, this, &importedFiles, &scannedPaths, &newLibraries); - for (const QString &path : qAsConst(allApplicationDirectories)) { - allImportPaths.maybeInsert(FilePath::fromString(path), Dialect::Qml); - findNewQmlApplicationInPath(FilePath::fromString(path), snapshot, this, &newLibraries); + for (const QString &pathStr : qAsConst(allApplicationDirectories)) { + Utils::FilePath path = Utils::FilePath::fromString(pathStr); + allImportPaths.maybeInsert(path, Dialect::Qml); + findNewQmlApplicationInPath(path, snapshot, this, &newLibraries); } + for (const Utils::FilePath &qrcPath : generatedQrc(allApplicationDirectories)) + updateQrcFile(qrcPath.toString()); updateSourceFiles(importedFiles, true); @@ -1668,4 +1714,47 @@ void ModelManagerInterface::resetCodeModel() updateImportPaths(); } +Utils::FilePath ModelManagerInterface::fileToSource(const Utils::FilePath &path) +{ + if (!path.scheme().isEmpty()) + return path; + for (const Utils::FilePath &p : m_applicationPaths) { + if (!p.isEmpty() && path.startsWith(p.path())) { + // if it is an applicationPath (i.e. in the build directory) + // try to use the path from the build dir as resource path + // and recover the path of the corresponding source file + QString reducedPath = path.path().mid(p.path().size()); + QString reversePath(reducedPath); + std::reverse(reversePath.begin(), reversePath.end()); + if (!reversePath.endsWith('/')) + reversePath.append('/'); + QrcParser::MatchResult res; + iterateQrcFiles(nullptr, + QrcResourceSelector::AllQrcResources, + [&](const QrcParser::ConstPtr &qrcFile) { + if (!qrcFile) + return; + QrcParser::MatchResult matchNow = qrcFile->longestReverseMatches( + reversePath); + + if (matchNow.matchDepth < res.matchDepth) + return; + if (matchNow.matchDepth == res.matchDepth) { + res.reversedPaths += matchNow.reversedPaths; + res.sourceFiles += matchNow.sourceFiles; + } else { + res = matchNow; + } + }); + std::sort(res.sourceFiles.begin(), res.sourceFiles.end()); + if (!res.sourceFiles.isEmpty()) { + return res.sourceFiles.first(); + } + qCWarning(qmljsLog) << "Could not find source file for file" << path + << "in application path" << p; + } + } + return path; +} + } // namespace QmlJS diff --git a/src/libs/qmljs/qmljsmodelmanagerinterface.h b/src/libs/qmljs/qmljsmodelmanagerinterface.h index 5a4f711a1be..4ed02c2c96d 100644 --- a/src/libs/qmljs/qmljsmodelmanagerinterface.h +++ b/src/libs/qmljs/qmljsmodelmanagerinterface.h @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -149,6 +150,7 @@ public: ProjectExplorer::Project *project = nullptr, bool addDirs = false, QrcResourceSelector resources = AllQrcResources); + Utils::FilePath fileToSource(const Utils::FilePath &file); QList projectInfos() const; bool containsProject(ProjectExplorer::Project *project) const; @@ -254,6 +256,7 @@ private: QmlJS::Snapshot m_validSnapshot; QmlJS::Snapshot m_newestSnapshot; PathsAndLanguages m_allImportPaths; + QList m_applicationPaths; QStringList m_defaultImportPaths; QmlJS::QmlLanguageBundles m_activeBundles; QmlJS::QmlLanguageBundles m_extendedBundles; diff --git a/src/libs/utils/qrcparser.cpp b/src/libs/utils/qrcparser.cpp index 24846ebf63b..58935f12100 100644 --- a/src/libs/utils/qrcparser.cpp +++ b/src/libs/utils/qrcparser.cpp @@ -57,6 +57,7 @@ public: const QLocale *locale = nullptr) const; void collectResourceFilesForSourceFile(const QString &sourceFile, QStringList *res, const QLocale *locale = nullptr) const; + QrcParser::MatchResult longestReverseMatches(const QString &) const; QStringList errorMessages() const; QStringList languages() const; @@ -65,6 +66,7 @@ private: const QStringList allUiLanguages(const QLocale *locale) const; SMap m_resources; + SMap m_reverseResources; SMap m_files; QStringList m_languages; QStringList m_errorMessages; @@ -207,6 +209,11 @@ void QrcParser::collectFilesAtPath(const QString &path, QStringList *res, const d->collectFilesAtPath(path, res, locale); } +QrcParser::MatchResult QrcParser::longestReverseMatches(const QString &p) const +{ + return d->longestReverseMatches(p); +} + /*! Returns \c true if \a path is a non-empty directory and matches \a locale. @@ -414,8 +421,14 @@ bool QrcParserPrivate::parseFile(const QString &path, const QString &contents) else accessPath = language + prefix + fileName; QStringList &resources = m_resources[accessPath]; - if (!resources.contains(filePath)) + if (!resources.contains(filePath)) { resources.append(filePath); + QString reversePath(accessPath); + std::reverse(reversePath.begin(), reversePath.end()); + if (!reversePath.endsWith('/')) + reversePath.append('/'); + m_reverseResources[reversePath].append(filePath); + } QStringList &files = m_files[filePath]; if (!files.contains(accessPath)) files.append(accessPath); @@ -517,6 +530,37 @@ void QrcParserPrivate::collectResourceFilesForSourceFile(const QString &sourceFi } } +QrcParser::MatchResult QrcParserPrivate::longestReverseMatches(const QString &reversePath) const +{ + QrcParser::MatchResult res; + if (reversePath.length() == 1) + return res; + auto lastMatch = m_reverseResources.end(); + qsizetype matchedUntil = 0; + for (qsizetype i = 1, j = 0; i < reversePath.size(); i = j + 1) { + j = reversePath.indexOf(u'/', i); + if (j == -1) + j = reversePath.size() - 1; + auto match = m_reverseResources.lowerBound(reversePath.mid(0, j + 1)); + QString pNow = reversePath.left(j + 1); + if (match == m_reverseResources.end() || match.key().left(j + 1) != pNow) + break; + ++res.matchDepth; + matchedUntil = j + 1; + lastMatch = match; + } + res.reversedPaths.clear(); + res.sourceFiles.clear(); + for (auto it = lastMatch; it != m_reverseResources.end() + && it.key().left(matchedUntil) == reversePath.left(matchedUntil); + ++it) { + res.reversedPaths.append(it.key()); + for (const QString &filePath : it.value()) + res.sourceFiles.append(Utils::FilePath::fromString(filePath)); + } + return res; +} + QStringList QrcParserPrivate::errorMessages() const { return m_errorMessages; diff --git a/src/libs/utils/qrcparser.h b/src/libs/utils/qrcparser.h index 665df445484..3027076a97e 100644 --- a/src/libs/utils/qrcparser.h +++ b/src/libs/utils/qrcparser.h @@ -26,6 +26,7 @@ #pragma once #include "utils_global.h" +#include "utils/filepath.h" #include #include @@ -47,12 +48,20 @@ class QrcCachePrivate; class QTCREATOR_UTILS_EXPORT QrcParser { public: + struct MatchResult + { + int matchDepth = {}; + QStringList reversedPaths; + QList sourceFiles; + }; + typedef QSharedPointer Ptr; typedef QSharedPointer ConstPtr; ~QrcParser(); bool parseFile(const QString &path, const QString &contents); QString firstFileAtPath(const QString &path, const QLocale &locale) const; void collectFilesAtPath(const QString &path, QStringList *res, const QLocale *locale = nullptr) const; + MatchResult longestReverseMatches(const QString &) const; bool hasDirAtPath(const QString &path, const QLocale *locale = nullptr) const; void collectFilesInPath(const QString &path, QMap *res, bool addDirs = false, const QLocale *locale = nullptr) const; diff --git a/src/plugins/qmljseditor/qmljseditor.cpp b/src/plugins/qmljseditor/qmljseditor.cpp index 894e0be8d99..9052ca40f13 100644 --- a/src/plugins/qmljseditor/qmljseditor.cpp +++ b/src/plugins/qmljseditor/qmljseditor.cpp @@ -771,7 +771,8 @@ void QmlJSEditorWidget::findLinkAt(const QTextCursor &cursor, const QList imports = semanticInfo.document->bind()->imports(); for (const ImportInfo &import : imports) { if (import.ast() == importAst && import.type() == ImportType::File) { - Utils::Link link(Utils::FilePath::fromString(import.path())); + Utils::Link link( + m_modelManager->fileToSource(FilePath::fromString(import.path()))); link.linkTextStart = importAst->firstSourceLocation().begin(); link.linkTextEnd = importAst->lastSourceLocation().end(); processLinkCallback(Utils::Link()); @@ -793,11 +794,9 @@ void QmlJSEditorWidget::findLinkAt(const QTextCursor &cursor, processLinkCallback(link); return; } - const QString relative = QString::fromLatin1("%1/%2").arg( - semanticInfo.document->path(), - text); - if (QFileInfo::exists(relative)) { - link.targetFilePath = Utils::FilePath::fromString(relative); + const Utils::FilePath relative = Utils::FilePath::fromString(semanticInfo.document->path()).pathAppended(text); + if (relative.exists()) { + link.targetFilePath = m_modelManager->fileToSource(relative); processLinkCallback(link); return; } @@ -814,7 +813,7 @@ void QmlJSEditorWidget::findLinkAt(const QTextCursor &cursor, return processLinkCallback(Utils::Link()); Utils::Link link; - link.targetFilePath = Utils::FilePath::fromString(fileName); + link.targetFilePath = m_modelManager->fileToSource(FilePath::fromString(fileName)); link.targetLine = line; link.targetColumn = column - 1; // adjust the column diff --git a/src/plugins/qmljseditor/qmljsfindreferences.cpp b/src/plugins/qmljseditor/qmljsfindreferences.cpp index e0a0f3269aa..38717948ee6 100644 --- a/src/plugins/qmljseditor/qmljsfindreferences.cpp +++ b/src/plugins/qmljseditor/qmljsfindreferences.cpp @@ -28,10 +28,11 @@ #include #include #include -#include #include +#include #include #include +#include #include #include @@ -745,6 +746,7 @@ public: future->waitForResume(); if (future->isCanceled()) return usages; + ModelManagerInterface *modelManager = ModelManagerInterface::instance(); Document::Ptr doc = context->snapshot().document(fileName); if (!doc) return usages; @@ -753,7 +755,7 @@ public: FindUsages findUsages(doc, context); const FindUsages::Result results = findUsages(name, scope); for (const SourceLocation &loc : results) - usages.append(Usage(fileName, matchingLine(loc.offset, doc->source()), loc.startLine, loc.startColumn - 1, loc.length)); + usages.append(Usage(modelManager->fileToSource(Utils::FilePath::fromString(fileName)).toString(), matchingLine(loc.offset, doc->source()), loc.startLine, loc.startColumn - 1, loc.length)); if (future->isPaused()) future->waitForResume(); return usages; @@ -896,8 +898,9 @@ static void find_helper(QFutureInterface &future, QStringList files; for (const Document::Ptr &doc : qAsConst(snapshot)) { // ### skip files that don't contain the name token - files.append(doc->fileName()); + files.append(modelManager->fileToSource(Utils::FilePath::fromString(doc->fileName())).toString()); } + files = Utils::filteredUnique(files); future.setProgressRange(0, files.size()); @@ -976,11 +979,23 @@ QList FindReferences::findUsageOfType(const QString &file QmlJS::Snapshot snapshot = modelManager->snapshot(); + QSet docDone; for (const QmlJS::Document::Ptr &doc : qAsConst(snapshot)) { - FindTypeUsages findUsages(doc, context); + QString sourceFile = modelManager->fileToSource(Utils::FilePath::fromString(doc->fileName())).toString(); + if (docDone.contains(sourceFile)) + continue; + docDone.insert(sourceFile); + QmlJS::Document::Ptr sourceDoc = doc; + if (sourceFile != doc->fileName()) + sourceDoc = snapshot.document(sourceFile); + FindTypeUsages findUsages(sourceDoc, context); const FindTypeUsages::Result results = findUsages(typeName, targetValue); for (const SourceLocation &loc : results) { - usages.append(Usage(doc->fileName(), matchingLine(loc.offset, doc->source()), loc.startLine, loc.startColumn - 1, loc.length)); + usages.append(Usage(sourceFile, + matchingLine(loc.offset, doc->source()), + loc.startLine, + loc.startColumn - 1, + loc.length)); } } return usages;