diff --git a/src/libs/qmljs/qmljsbind.cpp b/src/libs/qmljs/qmljsbind.cpp index 0c31052bfd00974d4608f4e8d54f99d8b59ba57a..daa047c50e61914dc16ab5c35b093d67f7930570 100644 --- a/src/libs/qmljs/qmljsbind.cpp +++ b/src/libs/qmljs/qmljsbind.cpp @@ -59,6 +59,16 @@ QStringList Bind::includedScripts() const return _includedScripts; } +QStringList Bind::fileImports() const +{ + return _fileImports; +} + +QStringList Bind::libraryImports() const +{ + return _libraryImports; +} + Interpreter::ObjectValue *Bind::currentObjectValue() const { return _currentObjectValue; @@ -205,8 +215,14 @@ bool Bind::visit(AST::Program *) return true; } -bool Bind::visit(UiImport *) +bool Bind::visit(UiImport *ast) { + if (ast->importUri) { + _libraryImports += toString(ast->importUri, QLatin1Char('/')); + } else if (ast->fileName) { + _fileImports += ast->fileName->asString(); + } + return false; } diff --git a/src/libs/qmljs/qmljsbind.h b/src/libs/qmljs/qmljsbind.h index e9f5fef931f9857c287dee83d1f4ac88ff81910f..82ed248ce53729aa14cea86012ebb3bea41e6082 100644 --- a/src/libs/qmljs/qmljsbind.h +++ b/src/libs/qmljs/qmljsbind.h @@ -51,6 +51,8 @@ public: virtual ~Bind(); QStringList includedScripts() const; + QStringList fileImports() const; + QStringList libraryImports() const; Interpreter::ObjectValue *currentObjectValue() const; Interpreter::ObjectValue *idEnvironment() const; @@ -100,6 +102,9 @@ private: QHash<AST::Node *, Interpreter::ObjectValue *> _qmlObjects; QStringList _includedScripts; + + QStringList _fileImports; + QStringList _libraryImports; }; } // end of namespace Qml diff --git a/src/plugins/qmljseditor/qmljsmodelmanager.cpp b/src/plugins/qmljseditor/qmljsmodelmanager.cpp index d21bda3660fbfccc9a6e8c02a8a7b95ca4fecb5b..3bd87c0659f624bc4831a0a05d1740064a0e5314 100644 --- a/src/plugins/qmljseditor/qmljsmodelmanager.cpp +++ b/src/plugins/qmljseditor/qmljsmodelmanager.cpp @@ -36,11 +36,11 @@ #include <coreplugin/progressmanager/progressmanager.h> #include <coreplugin/mimedatabase.h> #include <qmljs/qmljsinterpreter.h> +#include <qmljs/qmljsbind.h> #include <qmljs/parser/qmldirparser_p.h> #include <texteditor/itexteditor.h> #include <QDir> -#include <QDirIterator> #include <QFile> #include <QFileInfo> #include <QLibraryInfo> @@ -74,7 +74,6 @@ ModelManager::ModelManager(QObject *parent): m_defaultImportPaths << environmentImportPaths(); m_defaultImportPaths << QLibraryInfo::location(QLibraryInfo::ImportsPath); - refreshSourceDirectories(m_defaultImportPaths); } void ModelManager::loadQmlTypeDescriptions() @@ -100,19 +99,7 @@ Snapshot ModelManager::snapshot() const void ModelManager::updateSourceFiles(const QStringList &files) { - // for files that are not yet in the snapshot, scan the whole directory - QStringList filesToParse; - QSet<QString> sourceDirectories; - - foreach (const QString &file, files) { - if (! _snapshot.document(file)) - sourceDirectories.insert(QFileInfo(file).path()); - else - filesToParse.append(file); - } - - refreshSourceFiles(filesToParse); - refreshSourceDirectories(sourceDirectories.toList()); + refreshSourceFiles(files); } QFuture<void> ModelManager::refreshSourceFiles(const QStringList &sourceFiles) @@ -148,42 +135,6 @@ QFuture<void> ModelManager::refreshSourceFiles(const QStringList &sourceFiles) return result; } -void ModelManager::updateSourceDirectories(const QStringList &directories) -{ - refreshSourceDirectories(directories); -} - -QFuture<void> ModelManager::refreshSourceDirectories(const QStringList &sourceDirectories) -{ - if (sourceDirectories.isEmpty()) { - return QFuture<void>(); - } - - const QMap<QString, WorkingCopy> workingCopy = buildWorkingCopyList(); - - QFuture<void> result = QtConcurrent::run(&ModelManager::parseDirectories, - workingCopy, sourceDirectories, - this); - - if (m_synchronizer.futures().size() > 10) { - QList<QFuture<void> > futures = m_synchronizer.futures(); - - m_synchronizer.clearFutures(); - - foreach (QFuture<void> future, futures) { - if (! (future.isFinished() || future.isCanceled())) - m_synchronizer.addFuture(future); - } - } - - m_synchronizer.addFuture(result); - - m_core->progressManager()->addTask(result, tr("Indexing"), - QmlJSEditor::Constants::TASK_INDEX); - - return result; -} - QMap<QString, ModelManager::WorkingCopy> ModelManager::buildWorkingCopyList() { QMap<QString, WorkingCopy> workingCopy; @@ -223,6 +174,106 @@ void ModelManager::onLibraryInfoUpdated(const QString &path, const LibraryInfo & _snapshot.insertLibraryInfo(path, info); } +static QStringList qmlFilesInDirectory(const QString &path) +{ + // ### It would suffice to build pattern once. This function needs to be thread-safe. + Core::MimeDatabase *db = Core::ICore::instance()->mimeDatabase(); + Core::MimeType jsSourceTy = db->findByType(QmlJSEditor::Constants::JS_MIMETYPE); + Core::MimeType qmlSourceTy = db->findByType(QmlJSEditor::Constants::QML_MIMETYPE); + + QStringList pattern; + foreach (const QRegExp &glob, jsSourceTy.globPatterns()) + pattern << glob.pattern(); + foreach (const QRegExp &glob, qmlSourceTy.globPatterns()) + pattern << glob.pattern(); + + QStringList files; + + const QDir dir(path); + foreach (const QFileInfo &fi, dir.entryInfoList(pattern, QDir::Files)) + files += fi.absoluteFilePath(); + + return files; +} + +static void findNewImplicitImports(const Document::Ptr &doc, const Snapshot &snapshot, + QStringList *importedFiles, QSet<QString> *scannedPaths) +{ + // scan files that could be implicitly imported + // it's important we also do this for JS files, otherwise the isEmpty check will fail + if (snapshot.documentsInDirectory(doc->path()).isEmpty()) { + if (! scannedPaths->contains(doc->path())) { + *importedFiles += qmlFilesInDirectory(doc->path()); + scannedPaths->insert(doc->path()); + } + } +} + +static void findNewFileImports(const Document::Ptr &doc, const Snapshot &snapshot, + QStringList *importedFiles, QSet<QString> *scannedPaths) +{ + // scan files and directories that are explicitly imported + foreach (const QString &fileImport, doc->bind()->fileImports()) { + const QFileInfo importFileInfo(doc->path() + QLatin1Char('/') + fileImport); + const QString &importFilePath = importFileInfo.absoluteFilePath(); + if (importFileInfo.isFile()) { + if (! snapshot.document(importFilePath)) + *importedFiles += importFilePath; + } else if (importFileInfo.isDir()) { + if (snapshot.documentsInDirectory(importFilePath).isEmpty()) { + if (! scannedPaths->contains(importFilePath)) { + *importedFiles += qmlFilesInDirectory(importFilePath); + scannedPaths->insert(importFilePath); + } + } + } + } +} + +static void findNewLibraryImports(const Document::Ptr &doc, const Snapshot &snapshot, + ModelManager *modelManager, + QStringList *importedFiles, QSet<QString> *scannedPaths) +{ + // scan library imports + foreach (const QString &libraryImport, doc->bind()->libraryImports()) { + foreach (const QString &importPath, modelManager->importPaths()) { + QDir dir(importPath); + dir.cd(libraryImport); + const QString targetPath = dir.absolutePath(); + + // if we know there is a library, done + if (snapshot.libraryInfo(targetPath).isValid()) + break; + + // if there is a qmldir file, we found a new library! + if (dir.exists("qmldir")) { + QFile qmldirFile(dir.filePath("qmldir")); + qmldirFile.open(QFile::ReadOnly); + QString qmldirData = QString::fromUtf8(qmldirFile.readAll()); + + QmlDirParser qmldirParser; + qmldirParser.setSource(qmldirData); + qmldirParser.parse(); + + modelManager->emitLibraryInfoUpdated(QFileInfo(qmldirFile).absolutePath(), + LibraryInfo(qmldirParser)); + + // scan the qml files in the library + foreach (const QmlDirParser::Component &component, qmldirParser.components()) { + if (! component.fileName.isEmpty()) { + QFileInfo componentFileInfo(dir.filePath(component.fileName)); + const QString path = componentFileInfo.absolutePath(); + if (! scannedPaths->contains(path)) { + *importedFiles += qmlFilesInDirectory(path); + scannedPaths->insert(path); + } + } + } + } + } + } +} + void ModelManager::parse(QFutureInterface<void> &future, QMap<QString, WorkingCopy> workingCopy, QStringList files, @@ -232,10 +283,16 @@ void ModelManager::parse(QFutureInterface<void> &future, Core::MimeType jsSourceTy = db->findByType(QLatin1String("application/javascript")); Core::MimeType qmlSourceTy = db->findByType(QLatin1String("application/x-qml")); - future.setProgressRange(0, files.size()); + int progressRange = files.size(); + future.setProgressRange(0, progressRange); + + Snapshot snapshot = modelManager->_snapshot; + + // paths we have scanned for files and added to the files list + QSet<QString> scannedPaths; for (int i = 0; i < files.size(); ++i) { - future.setProgressValue(i); + future.setProgressValue(qreal(i) / files.size() * progressRange); const QString fileName = files.at(i); @@ -270,62 +327,24 @@ void ModelManager::parse(QFutureInterface<void> &future, Document::Ptr doc = Document::create(fileName); doc->setDocumentRevision(documentRevision); doc->setSource(contents); - - if (isQmlFile) - doc->parseQml(); - else - doc->parseJavaScript(); - - modelManager->emitDocumentUpdated(doc); - } - - future.setProgressValue(files.size()); -} - -void ModelManager::parseDirectories(QFutureInterface<void> &future, - QMap<QString, WorkingCopy> workingCopy, - QStringList directories, - ModelManager *modelManager) -{ - Core::MimeDatabase *db = Core::ICore::instance()->mimeDatabase(); - Core::MimeType jsSourceTy = db->findByType(QLatin1String("application/javascript")); - Core::MimeType qmlSourceTy = db->findByType(QLatin1String("application/x-qml")); - - QStringList pattern; - foreach (const QRegExp &glob, jsSourceTy.globPatterns()) - pattern << glob.pattern(); - foreach (const QRegExp &glob, qmlSourceTy.globPatterns()) - pattern << glob.pattern(); - pattern << QLatin1String("qmldir"); - - QStringList importedFiles; - QStringList qmldirFiles; - foreach (const QString &path, directories) { - QDirIterator fileIterator(path, pattern, QDir::Files, - QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); - while (fileIterator.hasNext()) { - fileIterator.next(); - if (fileIterator.fileName() == QLatin1String("qmldir")) - qmldirFiles << fileIterator.filePath(); - else - importedFiles << fileIterator.filePath(); + doc->parse(); + + // get list of referenced files not yet in snapshot or in directories already scanned + QStringList importedFiles; + findNewImplicitImports(doc, snapshot, &importedFiles, &scannedPaths); + findNewFileImports(doc, snapshot, &importedFiles, &scannedPaths); + findNewLibraryImports(doc, snapshot, modelManager, &importedFiles, &scannedPaths); + + // add new files to parse list + foreach (const QString &file, importedFiles) { + if (! files.contains(file)) + files.append(file); } - } - foreach (const QString &qmldirFilePath, qmldirFiles) { - QFile qmldirFile(qmldirFilePath); - qmldirFile.open(QFile::ReadOnly); - QString qmldirData = QString::fromUtf8(qmldirFile.readAll()); - - QmlDirParser qmldirParser; - qmldirParser.setSource(qmldirData); - qmldirParser.parse(); - - modelManager->emitLibraryInfoUpdated(QFileInfo(qmldirFilePath).path(), - LibraryInfo(qmldirParser)); + modelManager->emitDocumentUpdated(doc); } - parse(future, workingCopy, importedFiles, modelManager); + future.setProgressValue(progressRange); } // Check whether fileMimeType is the same or extends knownMimeType @@ -352,7 +371,14 @@ void ModelManager::setProjectImportPaths(const QStringList &importPaths) { m_projectImportPaths = importPaths; - refreshSourceDirectories(importPaths); + // check if any file in the snapshot imports something new in the new paths + Snapshot snapshot = _snapshot; + QStringList importedFiles; + QSet<QString> scannedPaths; + foreach (const Document::Ptr &doc, snapshot) + findNewLibraryImports(doc, snapshot, this, &importedFiles, &scannedPaths); + + updateSourceFiles(importedFiles); } QStringList ModelManager::importPaths() const diff --git a/src/plugins/qmljseditor/qmljsmodelmanager.h b/src/plugins/qmljseditor/qmljsmodelmanager.h index 8ca17240196f19a9e86a89062fc9ca938f738bd7..d83d6547c2c3b2f299b5febf531483c27d623171 100644 --- a/src/plugins/qmljseditor/qmljsmodelmanager.h +++ b/src/plugins/qmljseditor/qmljsmodelmanager.h @@ -55,7 +55,6 @@ public: virtual QmlJS::Snapshot snapshot() const; virtual void updateSourceFiles(const QStringList &files); - virtual void updateSourceDirectories(const QStringList &directories); void emitDocumentUpdated(QmlJS::Document::Ptr doc); void emitLibraryInfoUpdated(const QString &path, const QmlJS::LibraryInfo &info); @@ -82,7 +81,6 @@ protected: }; QFuture<void> refreshSourceFiles(const QStringList &sourceFiles); - QFuture<void> refreshSourceDirectories(const QStringList &sourceDirectories); QMap<QString, WorkingCopy> buildWorkingCopyList(); static void parse(QFutureInterface<void> &future, @@ -90,11 +88,6 @@ protected: QStringList files, ModelManager *modelManager); - static void parseDirectories(QFutureInterface<void> &future, - QMap<QString, WorkingCopy> workingCopy, - QStringList directories, - ModelManager *modelManager); - void loadQmlTypeDescriptions(); private: diff --git a/src/plugins/qmljseditor/qmljsmodelmanagerinterface.h b/src/plugins/qmljseditor/qmljsmodelmanagerinterface.h index e1a3ab18910afb0195a951e874d81a066d481652..280828c0e753b869b965b67df4ae126b42cc2838 100644 --- a/src/plugins/qmljseditor/qmljsmodelmanagerinterface.h +++ b/src/plugins/qmljseditor/qmljsmodelmanagerinterface.h @@ -54,7 +54,6 @@ public: virtual QmlJS::Snapshot snapshot() const = 0; virtual void updateSourceFiles(const QStringList &files) = 0; - virtual void updateSourceDirectories(const QStringList &directories) = 0; virtual void setProjectImportPaths(const QStringList &importPaths) = 0; virtual QStringList importPaths() const = 0; diff --git a/src/plugins/qmlprojectmanager/qmlproject.cpp b/src/plugins/qmlprojectmanager/qmlproject.cpp index f0f562330abcea9edeed3ccafa9b012f1913a39b..1b495017ce8f9ddc4759c74996a7e1d923f62992 100644 --- a/src/plugins/qmlprojectmanager/qmlproject.cpp +++ b/src/plugins/qmlprojectmanager/qmlproject.cpp @@ -106,7 +106,7 @@ void QmlProject::parseProject(RefreshOptions options) } if (m_projectItem) { m_projectItem.data()->setSourceDirectory(projectDir().path()); - m_modelManager->updateSourceDirectories(m_projectItem.data()->files()); + m_modelManager->updateSourceFiles(m_projectItem.data()->files()); } m_rootNode->refresh(); }