1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "qmljsbind.h"
27 #include "qmljsconstants.h"
28 // #include "qmljsfindexportedcpptypes.h"
29 #include "qmljsinterpreter.h"
30 #include "qmljsmodelmanagerinterface.h"
31 #include "qmljsplugindumper.h"
32 #include "qmljstypedescriptionreader.h"
33 #include "qmljsdialect.h"
34 #include "qmljsviewercontext.h"
35 #include "qmljsutils.h"
36 
37 // #include <cplusplus/cppmodelmanagerbase.h>
38 #include <utils/algorithm.h>
39 #include <utils/hostosinfo.h>
40 #include <utils/runextensions.h>
41 
42 #include <QDir>
43 #include <QFile>
44 #include <QFileInfo>
45 #include <QMetaObject>
46 #include <QRegExp>
47 #include <QTextDocument>
48 #include <QTextStream>
49 #include <QTimer>
50 #include <QtAlgorithms>
51 #include <QLibraryInfo>
52 
53 #include <stdio.h>
54 
55 namespace QmlJS {
56 
57 QMLJS_EXPORT Q_LOGGING_CATEGORY(qmljsLog, "qtc.qmljs.common")
58 
59 /*!
60     \class QmlJS::ModelManagerInterface
61     \brief The ModelManagerInterface class acts as an interface to the
62     global state of the QmlJS code model.
63     \sa QmlJS::Document QmlJS::Snapshot QmlJSTools::Internal::ModelManager
64 
65     The ModelManagerInterface is an interface for global state and actions in
66     the QmlJS code model. It is implemented by \l{QmlJSTools::Internal::ModelManager}
67     and the instance can be accessed through ModelManagerInterface::instance().
68 
69     One of its primary concerns is to keep the Snapshots it
70     maintains up to date by parsing documents and finding QML modules.
71 
72     It has a Snapshot that contains only valid Documents,
73     accessible through ModelManagerInterface::snapshot() and a Snapshot with
74     potentially more recent, but invalid documents that is exposed through
75     ModelManagerInterface::newestSnapshot().
76 */
77 
78 static ModelManagerInterface *g_instance = 0;
79 
80 const char qtQuickUISuffix[] = "ui.qml";
81 
environmentImportPaths()82 static QStringList environmentImportPaths()
83 {
84     QStringList paths;
85 
86     QByteArray envImportPath = qgetenv("QML_IMPORT_PATH");
87 
88     foreach (const QString &path, QString::fromLatin1(envImportPath)
89              .split(Utils::HostOsInfo::pathListSeparator(), QString::SkipEmptyParts)) {
90         QString canonicalPath = QDir(path).canonicalPath();
91         if (!canonicalPath.isEmpty() && !paths.contains(canonicalPath))
92             paths.append(canonicalPath);
93     }
94 
95     return paths;
96 }
97 
ModelManagerInterface(QObject * parent)98 ModelManagerInterface::ModelManagerInterface(QObject *parent)
99     : QObject(parent),
100       m_shouldScanImports(false),
101       m_defaultProject(0),
102       m_pluginDumper(new PluginDumper(this))
103 {
104     m_indexerEnabled = qgetenv("QTC_NO_CODE_INDEXER") != "1";
105 
106 #if 0
107     m_updateCppQmlTypesTimer = new QTimer(this);
108     m_updateCppQmlTypesTimer->setInterval(1000);
109     m_updateCppQmlTypesTimer->setSingleShot(true);
110     connect(m_updateCppQmlTypesTimer, &QTimer::timeout,
111             this, &ModelManagerInterface::startCppQmlTypeUpdate);
112 #endif
113 
114     m_asyncResetTimer = new QTimer(this);
115     m_asyncResetTimer->setInterval(15000);
116     m_asyncResetTimer->setSingleShot(true);
117     connect(m_asyncResetTimer, &QTimer::timeout, this, &ModelManagerInterface::resetCodeModel);
118 
119     qRegisterMetaType<QmlJS::Document::Ptr>("QmlJS::Document::Ptr");
120     qRegisterMetaType<QmlJS::LibraryInfo>("QmlJS::LibraryInfo");
121     qRegisterMetaType<QmlJS::Dialect>("QmlJS::Dialect");
122     qRegisterMetaType<QmlJS::PathAndLanguage>("QmlJS::PathAndLanguage");
123     qRegisterMetaType<QmlJS::PathsAndLanguages>("QmlJS::PathsAndLanguages");
124 
125     m_defaultProjectInfo.qtImportsPath = QFileInfo(
126                 QLibraryInfo::location(QLibraryInfo::ImportsPath)).canonicalFilePath();
127     m_defaultProjectInfo.qtQmlPath = QFileInfo(
128                 QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath)).canonicalFilePath();
129 
130     m_defaultImportPaths << environmentImportPaths();
131     updateImportPaths();
132 
133     Q_ASSERT(! g_instance);
134     g_instance = this;
135 }
136 
~ModelManagerInterface()137 ModelManagerInterface::~ModelManagerInterface()
138 {
139 #if 0
140     m_cppQmlTypesUpdater.cancel();
141     m_cppQmlTypesUpdater.waitForFinished();
142 #endif
143     Q_ASSERT(g_instance == this);
144     g_instance = 0;
145 }
146 
defaultLanguageMapping()147 static QHash<QString, Dialect> defaultLanguageMapping()
148 {
149     static QHash<QString, Dialect> res{
150         {QLatin1String("js"), Dialect::JavaScript},
151         {QLatin1String("qml"), Dialect::Qml},
152         {QLatin1String("qmltypes"), Dialect::QmlTypeInfo},
153         {QLatin1String("qmlproject"), Dialect::QmlProject},
154         {QLatin1String("json"), Dialect::Json},
155         {QLatin1String("qbs"), Dialect::QmlQbs},
156         {QLatin1String(qtQuickUISuffix), Dialect::QmlQtQuick2Ui}
157     };
158     return res;
159 }
160 
guessLanguageOfFile(const QString & fileName)161 Dialect ModelManagerInterface::guessLanguageOfFile(const QString &fileName)
162 {
163     QHash<QString, Dialect> lMapping;
164     if (instance())
165         lMapping = instance()->languageForSuffix();
166     else
167         lMapping = defaultLanguageMapping();
168     const QFileInfo info(fileName);
169     QString fileSuffix = info.suffix();
170 
171     /*
172      * I was reluctant to use complete suffix in all cases, because it is a huge
173      * change in behavior. But in case of .qml this should be safe.
174      */
175 
176     if (fileSuffix == QLatin1String("qml"))
177         fileSuffix = info.completeSuffix();
178 
179     return lMapping.value(fileSuffix, Dialect::NoLanguage);
180 }
181 
globPatternsForLanguages(const QList<Dialect> languages)182 QStringList ModelManagerInterface::globPatternsForLanguages(const QList<Dialect> languages)
183 {
184     QHash<QString, Dialect> lMapping;
185     if (instance())
186         lMapping = instance()->languageForSuffix();
187     else
188         lMapping = defaultLanguageMapping();
189     QStringList patterns;
190     QHashIterator<QString,Dialect> i(lMapping);
191     while (i.hasNext()) {
192         i.next();
193         if (languages.contains(i.value()))
194             patterns << QLatin1String("*.") + i.key();
195     }
196     return patterns;
197 }
198 
instance()199 ModelManagerInterface *ModelManagerInterface::instance()
200 {
201     return g_instance;
202 }
203 
writeWarning(const QString & msg)204 void ModelManagerInterface::writeWarning(const QString &msg)
205 {
206     if (ModelManagerInterface *i = instance())
207         i->writeMessageInternal(msg);
208     else
209         qCWarning(qmljsLog) << msg;
210 }
211 
workingCopy()212 ModelManagerInterface::WorkingCopy ModelManagerInterface::workingCopy()
213 {
214     if (ModelManagerInterface *i = instance())
215         return i->workingCopyInternal();
216     return WorkingCopy();
217 }
218 
activateScan()219 void ModelManagerInterface::activateScan()
220 {
221     if (!m_shouldScanImports) {
222         m_shouldScanImports = true;
223         updateImportPaths();
224     }
225 }
226 
languageForSuffix() const227 QHash<QString, Dialect> ModelManagerInterface::languageForSuffix() const
228 {
229     return defaultLanguageMapping();
230 }
231 
writeMessageInternal(const QString & msg) const232 void ModelManagerInterface::writeMessageInternal(const QString &msg) const
233 {
234     qCDebug(qmljsLog) << msg;
235 }
236 
workingCopyInternal() const237 ModelManagerInterface::WorkingCopy ModelManagerInterface::workingCopyInternal() const
238 {
239     ModelManagerInterface::WorkingCopy res;
240     return res;
241 }
242 
addTaskInternal(QFuture<void> result,const QString & msg,const char * taskId) const243 void ModelManagerInterface::addTaskInternal(QFuture<void> result, const QString &msg,
244                                             const char *taskId) const
245 {
246     Q_UNUSED(result);
247     qCDebug(qmljsLog) << "started " << taskId << " " << msg;
248 }
249 
loadQmlTypeDescriptionsInternal(const QString & resourcePath)250 void ModelManagerInterface::loadQmlTypeDescriptionsInternal(const QString &resourcePath)
251 {
252     const QDir typeFileDir(resourcePath + QLatin1String("/qml-type-descriptions"));
253     const QStringList qmlTypesExtensions = QStringList("*.qmltypes");
254     QFileInfoList qmlTypesFiles = typeFileDir.entryInfoList(
255                 qmlTypesExtensions,
256                 QDir::Files,
257                 QDir::Name);
258 
259     QStringList errors;
260     QStringList warnings;
261 
262     // filter out the actual Qt builtins
263     for (int i = 0; i < qmlTypesFiles.size(); ++i) {
264         if (qmlTypesFiles.at(i).baseName() == QLatin1String("builtins")) {
265             QFileInfoList list;
266             list.append(qmlTypesFiles.at(i));
267             CppQmlTypesLoader::defaultQtObjects =
268                     CppQmlTypesLoader::loadQmlTypes(list, &errors, &warnings);
269             qmlTypesFiles.removeAt(i);
270             break;
271         }
272     }
273 
274     // load the fallbacks for libraries
275     CppQmlTypesLoader::defaultLibraryObjects.unite(
276                 CppQmlTypesLoader::loadQmlTypes(qmlTypesFiles, &errors, &warnings));
277 
278     foreach (const QString &error, errors)
279         writeMessageInternal(error);
280     foreach (const QString &warning, warnings)
281         writeMessageInternal(warning);
282 }
283 
setDefaultProject(const ModelManagerInterface::ProjectInfo & pInfo,ProjectExplorer::Project * p)284 void ModelManagerInterface::setDefaultProject(const ModelManagerInterface::ProjectInfo &pInfo,
285                                               ProjectExplorer::Project *p)
286 {
287     QMutexLocker l(mutex());
288     m_defaultProject = p;
289     m_defaultProjectInfo = pInfo;
290 }
291 
snapshot() const292 Snapshot ModelManagerInterface::snapshot() const
293 {
294     QMutexLocker locker(&m_mutex);
295     return m_validSnapshot;
296 }
297 
newestSnapshot() const298 Snapshot ModelManagerInterface::newestSnapshot() const
299 {
300     QMutexLocker locker(&m_mutex);
301     return m_newestSnapshot;
302 }
303 
updateSourceFiles(const QStringList & files,bool emitDocumentOnDiskChanged)304 void ModelManagerInterface::updateSourceFiles(const QStringList &files,
305                                      bool emitDocumentOnDiskChanged)
306 {
307     if (!m_indexerEnabled)
308         return;
309     refreshSourceFiles(files, emitDocumentOnDiskChanged);
310 }
311 
cleanupFutures()312 void ModelManagerInterface::cleanupFutures()
313 {
314     if (m_futures.size() > 10) {
315         QList<QFuture<void> > futures = m_futures;
316         m_futures.clear();
317         foreach (const QFuture<void> &future, futures) {
318             if (!(future.isFinished() || future.isCanceled()))
319                 m_futures.append(future);
320         }
321     }
322 }
323 
refreshSourceFiles(const QStringList & sourceFiles,bool emitDocumentOnDiskChanged)324 QFuture<void> ModelManagerInterface::refreshSourceFiles(const QStringList &sourceFiles,
325                                                bool emitDocumentOnDiskChanged)
326 {
327     if (sourceFiles.isEmpty())
328         return QFuture<void>();
329 
330     QFuture<void> result = Utils::runAsync(&ModelManagerInterface::parse,
331                                            workingCopyInternal(), sourceFiles,
332                                            this, Dialect(Dialect::Qml),
333                                            emitDocumentOnDiskChanged);
334     cleanupFutures();
335     m_futures.append(result);
336 
337     if (sourceFiles.count() > 1)
338          addTaskInternal(result, tr("Parsing QML Files"), Constants::TASK_INDEX);
339 
340     if (sourceFiles.count() > 1 && !m_shouldScanImports) {
341         bool scan = false;
342         {
343             QMutexLocker l(&m_mutex);
344             if (!m_shouldScanImports) {
345                 m_shouldScanImports = true;
346                 scan = true;
347             }
348         }
349         if (scan)
350         updateImportPaths();
351     }
352 
353     return result;
354 }
355 
356 
fileChangedOnDisk(const QString & path)357 void ModelManagerInterface::fileChangedOnDisk(const QString &path)
358 {
359     Utils::runAsync(&ModelManagerInterface::parse,
360                     workingCopyInternal(), QStringList(path),
361                     this, Dialect(Dialect::AnyLanguage), true);
362 }
363 
removeFiles(const QStringList & files)364 void ModelManagerInterface::removeFiles(const QStringList &files)
365 {
366     emit aboutToRemoveFiles(files);
367 
368     QMutexLocker locker(&m_mutex);
369 
370     foreach (const QString &file, files) {
371         m_validSnapshot.remove(file);
372         m_newestSnapshot.remove(file);
373     }
374 }
375 
376 namespace {
pInfoLessThanActive(const ModelManagerInterface::ProjectInfo & p1,const ModelManagerInterface::ProjectInfo & p2)377 bool pInfoLessThanActive(const ModelManagerInterface::ProjectInfo &p1, const ModelManagerInterface::ProjectInfo &p2)
378 {
379     QStringList s1 = p1.activeResourceFiles;
380     QStringList s2 = p2.activeResourceFiles;
381     if (s1.size() < s2.size())
382         return true;
383     if (s1.size() > s2.size())
384         return false;
385     for (int i = 0; i < s1.size(); ++i) {
386         if (s1.at(i) < s2.at(i))
387             return true;
388         else if (s1.at(i) > s2.at(i))
389             return false;
390     }
391     return false;
392 }
393 
pInfoLessThanAll(const ModelManagerInterface::ProjectInfo & p1,const ModelManagerInterface::ProjectInfo & p2)394 bool pInfoLessThanAll(const ModelManagerInterface::ProjectInfo &p1, const ModelManagerInterface::ProjectInfo &p2)
395 {
396     QStringList s1 = p1.allResourceFiles;
397     QStringList s2 = p2.allResourceFiles;
398     if (s1.size() < s2.size())
399         return true;
400     if (s1.size() > s2.size())
401         return false;
402     for (int i = 0; i < s1.size(); ++i) {
403         if (s1.at(i) < s2.at(i))
404             return true;
405         else if (s1.at(i) > s2.at(i))
406             return false;
407     }
408     return false;
409 }
410 
pInfoLessThanImports(const ModelManagerInterface::ProjectInfo & p1,const ModelManagerInterface::ProjectInfo & p2)411 bool pInfoLessThanImports(const ModelManagerInterface::ProjectInfo &p1, const ModelManagerInterface::ProjectInfo &p2)
412 {
413     if (p1.qtQmlPath < p2.qtQmlPath)
414         return true;
415     if (p1.qtQmlPath > p2.qtQmlPath)
416         return false;
417     if (p1.qtImportsPath < p2.qtImportsPath)
418         return true;
419     if (p1.qtImportsPath > p2.qtImportsPath)
420         return false;
421     const PathsAndLanguages &s1 = p1.importPaths;
422     const PathsAndLanguages &s2 = p2.importPaths;
423     if (s1.size() < s2.size())
424         return true;
425     if (s1.size() > s2.size())
426         return false;
427     for (int i = 0; i < s1.size(); ++i) {
428         if (s1.at(i) < s2.at(i))
429             return true;
430         else if (s2.at(i) < s1.at(i))
431             return false;
432     }
433     return false;
434 }
435 
436 }
437 
iterateQrcFiles(ProjectExplorer::Project * project,QrcResourceSelector resources,std::function<void (QrcParser::ConstPtr)> callback)438 void ModelManagerInterface::iterateQrcFiles(ProjectExplorer::Project *project,
439                                             QrcResourceSelector resources,
440                                             std::function<void(QrcParser::ConstPtr)> callback)
441 {
442     QList<ProjectInfo> pInfos;
443     if (project) {
444         pInfos.append(projectInfo(project));
445     } else {
446         pInfos = projectInfos();
447         if (resources == ActiveQrcResources) // make the result predictable
448             Utils::sort(pInfos, &pInfoLessThanActive);
449         else
450             Utils::sort(pInfos, &pInfoLessThanAll);
451     }
452 
453     QSet<QString> pathsChecked;
454     foreach (const ModelManagerInterface::ProjectInfo &pInfo, pInfos) {
455         QStringList qrcFilePaths;
456         if (resources == ActiveQrcResources)
457             qrcFilePaths = pInfo.activeResourceFiles;
458         else
459             qrcFilePaths = pInfo.allResourceFiles;
460         foreach (const QString &qrcFilePath, qrcFilePaths) {
461             if (pathsChecked.contains(qrcFilePath))
462                 continue;
463             pathsChecked.insert(qrcFilePath);
464             QrcParser::ConstPtr qrcFile = m_qrcCache.parsedPath(qrcFilePath);
465             if (qrcFile.isNull())
466                 continue;
467             callback(qrcFile);
468         }
469     }
470 }
471 
qrcPathsForFile(const QString & file,const QLocale * locale,ProjectExplorer::Project * project,QrcResourceSelector resources)472 QStringList ModelManagerInterface::qrcPathsForFile(const QString &file, const QLocale *locale,
473                                                    ProjectExplorer::Project *project,
474                                                    QrcResourceSelector resources)
475 {
476     QStringList res;
477     iterateQrcFiles(project, resources, [&](QrcParser::ConstPtr qrcFile) {
478         qrcFile->collectResourceFilesForSourceFile(file, &res, locale);
479     });
480     return res;
481 }
482 
filesAtQrcPath(const QString & path,const QLocale * locale,ProjectExplorer::Project * project,QrcResourceSelector resources)483 QStringList ModelManagerInterface::filesAtQrcPath(const QString &path, const QLocale *locale,
484                                          ProjectExplorer::Project *project,
485                                          QrcResourceSelector resources)
486 {
487     QString normPath = QrcParser::normalizedQrcFilePath(path);
488     QStringList res;
489     iterateQrcFiles(project, resources, [&](QrcParser::ConstPtr qrcFile) {
490         qrcFile->collectFilesAtPath(normPath, &res, locale);
491     });
492     return res;
493 }
494 
filesInQrcPath(const QString & path,const QLocale * locale,ProjectExplorer::Project * project,bool addDirs,QrcResourceSelector resources)495 QMap<QString, QStringList> ModelManagerInterface::filesInQrcPath(const QString &path,
496                                                         const QLocale *locale,
497                                                         ProjectExplorer::Project *project,
498                                                         bool addDirs,
499                                                         QrcResourceSelector resources)
500 {
501     QString normPath = QrcParser::normalizedQrcDirectoryPath(path);
502     QMap<QString, QStringList> res;
503     iterateQrcFiles(project, resources, [&](QrcParser::ConstPtr qrcFile) {
504         qrcFile->collectFilesInPath(normPath, &res, addDirs, locale);
505     });
506     return res;
507 }
508 
projectInfos() const509 QList<ModelManagerInterface::ProjectInfo> ModelManagerInterface::projectInfos() const
510 {
511     QMutexLocker locker(&m_mutex);
512 
513     return m_projects.values();
514 }
515 
projectInfo(ProjectExplorer::Project * project,const ModelManagerInterface::ProjectInfo & defaultValue) const516 ModelManagerInterface::ProjectInfo ModelManagerInterface::projectInfo(
517         ProjectExplorer::Project *project,
518         const ModelManagerInterface::ProjectInfo &defaultValue) const
519 {
520     QMutexLocker locker(&m_mutex);
521 
522     return m_projects.value(project, defaultValue);
523 }
524 
updateProjectInfo(const ProjectInfo & pinfo,ProjectExplorer::Project * p)525 void ModelManagerInterface::updateProjectInfo(const ProjectInfo &pinfo, ProjectExplorer::Project *p)
526 {
527     if (! pinfo.isValid() || !p || !m_indexerEnabled)
528         return;
529 
530     Snapshot snapshot;
531     ProjectInfo oldInfo;
532     {
533         QMutexLocker locker(&m_mutex);
534         oldInfo = m_projects.value(p);
535         m_projects.insert(p, pinfo);
536         if (p == m_defaultProject)
537             m_defaultProjectInfo = pinfo;
538         snapshot = m_validSnapshot;
539     }
540 
541     if (oldInfo.qmlDumpPath != pinfo.qmlDumpPath
542             || oldInfo.qmlDumpEnvironment != pinfo.qmlDumpEnvironment) {
543         m_pluginDumper->scheduleRedumpPlugins();
544         m_pluginDumper->scheduleMaybeRedumpBuiltins(pinfo);
545     }
546 
547 
548     updateImportPaths();
549 
550     // remove files that are no longer in the project and have been deleted
551     QStringList deletedFiles;
552     foreach (const QString &oldFile, oldInfo.sourceFiles) {
553         if (snapshot.document(oldFile)
554                 && !pinfo.sourceFiles.contains(oldFile)
555                 && !QFile::exists(oldFile)) {
556             deletedFiles += oldFile;
557         }
558     }
559     removeFiles(deletedFiles);
560     foreach (const QString &oldFile, deletedFiles)
561         m_fileToProject.remove(oldFile, p);
562 
563     // parse any files not yet in the snapshot
564     QStringList newFiles;
565     foreach (const QString &file, pinfo.sourceFiles) {
566         if (!snapshot.document(file))
567             newFiles += file;
568     }
569     foreach (const QString &newFile, newFiles)
570         m_fileToProject.insert(newFile, p);
571     updateSourceFiles(newFiles, false);
572 
573     // update qrc cache
574     m_qrcContents = pinfo.resourceFileContents;
575     foreach (const QString &newQrc, pinfo.allResourceFiles)
576         m_qrcCache.addPath(newQrc, m_qrcContents.value(newQrc));
577     foreach (const QString &oldQrc, oldInfo.allResourceFiles)
578         m_qrcCache.removePath(oldQrc);
579 
580     int majorVersion, minorVersion, patchVersion;
581     // dump builtin types if the shipped definitions are probably outdated and the
582     // Qt version ships qmlplugindump
583     if (::sscanf(pinfo.qtVersionString.toLatin1().constData(), "%d.%d.%d",
584            &majorVersion, &minorVersion, &patchVersion) != 3)
585         majorVersion = minorVersion = patchVersion = -1;
586 
587     if (majorVersion > 4 || (majorVersion == 4 && (minorVersion > 8 || (minorVersion == 8
588                                                                        && patchVersion >= 5)))) {
589         m_pluginDumper->loadBuiltinTypes(pinfo);
590     }
591 
592     emit projectInfoUpdated(pinfo);
593 }
594 
595 
removeProjectInfo(ProjectExplorer::Project * project)596 void ModelManagerInterface::removeProjectInfo(ProjectExplorer::Project *project)
597 {
598     ProjectInfo info;
599     info.sourceFiles.clear();
600     // update with an empty project info to clear data
601     updateProjectInfo(info, project);
602 
603     {
604         QMutexLocker locker(&m_mutex);
605         m_projects.remove(project);
606     }
607 }
608 
609 /*!
610     Returns project info with summarized info for \a path
611 
612     \note Project pointer will be empty
613  */
projectInfoForPath(const QString & path) const614 ModelManagerInterface::ProjectInfo ModelManagerInterface::projectInfoForPath(const QString &path) const
615 {
616     QList<ProjectInfo> infos = allProjectInfosForPath(path);
617 
618     ProjectInfo res;
619     foreach (const ProjectInfo &pInfo, infos) {
620         if (res.qtImportsPath.isEmpty())
621             res.qtImportsPath = pInfo.qtImportsPath;
622         if (res.qtQmlPath.isEmpty())
623             res.qtQmlPath = pInfo.qtQmlPath;
624         for (int i = 0; i < pInfo.importPaths.size(); ++i)
625             res.importPaths.maybeInsert(pInfo.importPaths.at(i));
626     }
627     return res;
628 }
629 
630 /*!
631     Returns list of project infos for \a path
632  */
allProjectInfosForPath(const QString & path) const633 QList<ModelManagerInterface::ProjectInfo> ModelManagerInterface::allProjectInfosForPath(const QString &path) const
634 {
635     QList<ProjectExplorer::Project *> projects;
636     {
637         QMutexLocker locker(&m_mutex);
638         projects = m_fileToProject.values(path);
639         if (projects.isEmpty()) {
640             QFileInfo fInfo(path);
641             projects = m_fileToProject.values(fInfo.canonicalFilePath());
642         }
643     }
644     QList<ProjectInfo> infos;
645     foreach (ProjectExplorer::Project *project, projects) {
646         ProjectInfo info = projectInfo(project);
647         if (info.isValid())
648             infos.append(info);
649     }
650     std::sort(infos.begin(), infos.end(), &pInfoLessThanImports);
651     infos.append(m_defaultProjectInfo);
652     return infos;
653 }
654 
isIdle() const655 bool ModelManagerInterface::isIdle() const
656 {
657     return m_futures.isEmpty();
658 }
659 
emitDocumentChangedOnDisk(Document::Ptr doc)660 void ModelManagerInterface::emitDocumentChangedOnDisk(Document::Ptr doc)
661 { emit documentChangedOnDisk(doc); }
662 
updateQrcFile(const QString & path)663 void ModelManagerInterface::updateQrcFile(const QString &path)
664 {
665     m_qrcCache.updatePath(path, m_qrcContents.value(path));
666 }
667 
updateDocument(Document::Ptr doc)668 void ModelManagerInterface::updateDocument(Document::Ptr doc)
669 {
670     {
671         QMutexLocker locker(&m_mutex);
672         m_validSnapshot.insert(doc);
673         m_newestSnapshot.insert(doc, true);
674     }
675     emit documentUpdated(doc);
676 }
677 
updateLibraryInfo(const QString & path,const LibraryInfo & info)678 void ModelManagerInterface::updateLibraryInfo(const QString &path, const LibraryInfo &info)
679 {
680     if (!info.pluginTypeInfoError().isEmpty())
681         qCDebug(qmljsLog) << "Dumping errors for " << path << ":" << info.pluginTypeInfoError();
682 
683     {
684         QMutexLocker locker(&m_mutex);
685         m_validSnapshot.insertLibraryInfo(path, info);
686         m_newestSnapshot.insertLibraryInfo(path, info);
687     }
688     // only emit if we got new useful information
689     if (info.isValid())
690         emit libraryInfoUpdated(path, info);
691 }
692 
filesInDirectoryForLanguages(const QString & path,QList<Dialect> languages)693 static QStringList filesInDirectoryForLanguages(const QString &path, QList<Dialect> languages)
694 {
695     const QStringList pattern = ModelManagerInterface::globPatternsForLanguages(languages);
696     QStringList files;
697 
698     const QDir dir(path);
699     foreach (const QFileInfo &fi, dir.entryInfoList(pattern, QDir::Files))
700         files += fi.absoluteFilePath();
701 
702     return files;
703 }
704 
findNewImplicitImports(const Document::Ptr & doc,const Snapshot & snapshot,QStringList * importedFiles,QSet<QString> * scannedPaths)705 static void findNewImplicitImports(const Document::Ptr &doc, const Snapshot &snapshot,
706                             QStringList *importedFiles, QSet<QString> *scannedPaths)
707 {
708     // scan files that could be implicitly imported
709     // it's important we also do this for JS files, otherwise the isEmpty check will fail
710     if (snapshot.documentsInDirectory(doc->path()).isEmpty()) {
711         if (! scannedPaths->contains(doc->path())) {
712             *importedFiles += filesInDirectoryForLanguages(doc->path(),
713                                                   doc->language().companionLanguages());
714             scannedPaths->insert(doc->path());
715         }
716     }
717 }
718 
findNewFileImports(const Document::Ptr & doc,const Snapshot & snapshot,QStringList * importedFiles,QSet<QString> * scannedPaths)719 static void findNewFileImports(const Document::Ptr &doc, const Snapshot &snapshot,
720                         QStringList *importedFiles, QSet<QString> *scannedPaths)
721 {
722     // scan files and directories that are explicitly imported
723     foreach (const ImportInfo &import, doc->bind()->imports()) {
724         const QString &importName = import.path();
725         if (import.type() == ImportType::File) {
726             if (! snapshot.document(importName))
727                 *importedFiles += importName;
728         } else if (import.type() == ImportType::Directory) {
729             if (snapshot.documentsInDirectory(importName).isEmpty()) {
730                 if (! scannedPaths->contains(importName)) {
731                     *importedFiles += filesInDirectoryForLanguages(importName,
732                                                           doc->language().companionLanguages());
733                     scannedPaths->insert(importName);
734                 }
735             }
736         } else if (import.type() == ImportType::QrcFile) {
737             QStringList importPaths = ModelManagerInterface::instance()->filesAtQrcPath(importName);
738             foreach (const QString &importPath, importPaths) {
739                 if (! snapshot.document(importPath))
740                     *importedFiles += importPath;
741             }
742         } else if (import.type() == ImportType::QrcDirectory) {
743             QMapIterator<QString,QStringList> dirContents(ModelManagerInterface::instance()->filesInQrcPath(importName));
744             while (dirContents.hasNext()) {
745                 dirContents.next();
746                 if (ModelManagerInterface::guessLanguageOfFile(dirContents.key()).isQmlLikeOrJsLanguage()) {
747                     foreach (const QString &filePath, dirContents.value()) {
748                         if (! snapshot.document(filePath))
749                             *importedFiles += filePath;
750                     }
751                 }
752             }
753         }
754     }
755 }
756 
findNewQmlLibraryInPath(const QString & path,const Snapshot & snapshot,ModelManagerInterface * modelManager,QStringList * importedFiles,QSet<QString> * scannedPaths,QSet<QString> * newLibraries,bool ignoreMissing)757 static bool findNewQmlLibraryInPath(const QString &path,
758                                     const Snapshot &snapshot,
759                                     ModelManagerInterface *modelManager,
760                                     QStringList *importedFiles,
761                                     QSet<QString> *scannedPaths,
762                                     QSet<QString> *newLibraries,
763                                     bool ignoreMissing)
764 {
765     // if we know there is a library, done
766     const LibraryInfo &existingInfo = snapshot.libraryInfo(path);
767     if (existingInfo.isValid())
768         return true;
769     if (newLibraries->contains(path))
770         return true;
771     // if we looked at the path before, done
772     if (existingInfo.wasScanned())
773         return false;
774 
775     const QDir dir(path);
776     QFile qmldirFile(dir.filePath(QLatin1String("qmldir")));
777     if (!qmldirFile.exists()) {
778         if (!ignoreMissing) {
779             LibraryInfo libraryInfo(LibraryInfo::NotFound);
780             modelManager->updateLibraryInfo(path, libraryInfo);
781         }
782         return false;
783     }
784 
785     if (Utils::HostOsInfo::isWindowsHost()) {
786         // QTCREATORBUG-3402 - be case sensitive even here?
787     }
788 
789     // found a new library!
790     if (!qmldirFile.open(QFile::ReadOnly))
791         return false;
792     QString qmldirData = QString::fromUtf8(qmldirFile.readAll());
793 
794     QmlDirParser qmldirParser;
795     qmldirParser.parse(qmldirData);
796 
797     const QString libraryPath = QFileInfo(qmldirFile).absolutePath();
798     newLibraries->insert(libraryPath);
799     modelManager->updateLibraryInfo(libraryPath, LibraryInfo(qmldirParser));
800     modelManager->loadPluginTypes(QFileInfo(libraryPath).canonicalFilePath(), libraryPath,
801                 QString(), QString());
802 
803     // scan the qml files in the library
804     foreach (const QmlDirParser::Component &component, qmldirParser.components()) {
805         if (! component.fileName.isEmpty()) {
806             const QFileInfo componentFileInfo(dir.filePath(component.fileName));
807             const QString path = QDir::cleanPath(componentFileInfo.absolutePath());
808             if (! scannedPaths->contains(path)) {
809                 *importedFiles += filesInDirectoryForLanguages(path,
810                      Dialect(Dialect::AnyLanguage).companionLanguages());
811                 scannedPaths->insert(path);
812             }
813         }
814     }
815 
816     return true;
817 }
818 
modulePath(const ImportInfo & import,const QStringList & paths)819 static QString modulePath(const ImportInfo &import, const QStringList &paths)
820 {
821     if (!import.version().isValid())
822         return QString();
823     return modulePath(import.name(), import.version().toString(), paths);
824 }
825 
findNewLibraryImports(const Document::Ptr & doc,const Snapshot & snapshot,ModelManagerInterface * modelManager,QStringList * importedFiles,QSet<QString> * scannedPaths,QSet<QString> * newLibraries)826 static void findNewLibraryImports(const Document::Ptr &doc, const Snapshot &snapshot,
827                            ModelManagerInterface *modelManager,
828                            QStringList *importedFiles, QSet<QString> *scannedPaths, QSet<QString> *newLibraries)
829 {
830     // scan current dir
831     findNewQmlLibraryInPath(doc->path(), snapshot, modelManager,
832                             importedFiles, scannedPaths, newLibraries, false);
833 
834     // scan dir and lib imports
835     const QStringList importPaths = modelManager->importPathsNames();
836     foreach (const ImportInfo &import, doc->bind()->imports()) {
837         if (import.type() == ImportType::Directory) {
838             const QString targetPath = import.path();
839             findNewQmlLibraryInPath(targetPath, snapshot, modelManager,
840                                     importedFiles, scannedPaths, newLibraries, false);
841         }
842 
843         if (import.type() == ImportType::Library) {
844             const QString libraryPath = modulePath(import, importPaths);
845             if (libraryPath.isEmpty())
846                 continue;
847             findNewQmlLibraryInPath(libraryPath, snapshot, modelManager, importedFiles,
848                                     scannedPaths, newLibraries, false);
849         }
850     }
851 }
852 
parseLoop(QSet<QString> & scannedPaths,QSet<QString> & newLibraries,WorkingCopy workingCopy,QStringList files,ModelManagerInterface * modelManager,Dialect mainLanguage,bool emitDocChangedOnDisk,std::function<bool (qreal)> reportProgress)853 void ModelManagerInterface::parseLoop(QSet<QString> &scannedPaths,
854                              QSet<QString> &newLibraries,
855                              WorkingCopy workingCopy,
856                              QStringList files,
857                              ModelManagerInterface *modelManager,
858                              Dialect mainLanguage,
859                              bool emitDocChangedOnDisk,
860                              std::function<bool(qreal)> reportProgress)
861 {
862     for (int i = 0; i < files.size(); ++i) {
863         if (!reportProgress(qreal(i) / files.size()))
864             return;
865 
866         const QString fileName = files.at(i);
867 
868         Dialect language = guessLanguageOfFile(fileName);
869         if (language == Dialect::NoLanguage) {
870             if (fileName.endsWith(QLatin1String(".qrc")))
871                 modelManager->updateQrcFile(fileName);
872             continue;
873         }
874         if (language == Dialect::Qml
875                 && (mainLanguage == Dialect::QmlQtQuick2))
876             language = mainLanguage;
877         if (language == Dialect::Qml && mainLanguage == Dialect::QmlQtQuick2Ui)
878             language = Dialect::QmlQtQuick2;
879         if (language == Dialect::QmlTypeInfo || language == Dialect::QmlProject)
880             continue;
881         QString contents;
882         int documentRevision = 0;
883 
884         if (workingCopy.contains(fileName)) {
885             QPair<QString, int> entry = workingCopy.get(fileName);
886             contents = entry.first;
887             documentRevision = entry.second;
888         } else {
889             QFile inFile(fileName);
890 
891             if (inFile.open(QIODevice::ReadOnly)) {
892                 QTextStream ins(&inFile);
893                 contents = ins.readAll();
894                 inFile.close();
895             } else {
896                 continue;
897             }
898         }
899 
900         Document::MutablePtr doc = Document::create(fileName, language);
901         doc->setEditorRevision(documentRevision);
902         doc->setSource(contents);
903         doc->parse();
904 
905         // update snapshot. requires synchronization, but significantly reduces amount of file
906         // system queries for library imports because queries are cached in libraryInfo
907         const Snapshot snapshot = modelManager->snapshot();
908 
909         // get list of referenced files not yet in snapshot or in directories already scanned
910         QStringList importedFiles;
911         findNewImplicitImports(doc, snapshot, &importedFiles, &scannedPaths);
912         findNewFileImports(doc, snapshot, &importedFiles, &scannedPaths);
913         findNewLibraryImports(doc, snapshot, modelManager, &importedFiles, &scannedPaths, &newLibraries);
914 
915         // add new files to parse list
916         foreach (const QString &file, importedFiles) {
917             if (! files.contains(file))
918                 files.append(file);
919         }
920 
921         modelManager->updateDocument(doc);
922         if (emitDocChangedOnDisk)
923             modelManager->emitDocumentChangedOnDisk(doc);
924     }
925 }
926 
927 class FutureReporter
928 {
929 public:
FutureReporter(QFutureInterface<void> & future,int multiplier=100,int base=0)930     FutureReporter(QFutureInterface<void> &future, int multiplier = 100, int base = 0)
931         :future(future), multiplier(multiplier), base(base)
932     { }
operator ()(qreal val)933     bool operator()(qreal val)
934     {
935         if (future.isCanceled())
936             return false;
937         future.setProgressValue(int(base + multiplier * val));
938         return true;
939     }
940 private:
941     QFutureInterface<void> &future;
942     int multiplier;
943     int base;
944 };
945 
parse(QFutureInterface<void> & future,WorkingCopy workingCopy,QStringList files,ModelManagerInterface * modelManager,Dialect mainLanguage,bool emitDocChangedOnDisk)946 void ModelManagerInterface::parse(QFutureInterface<void> &future,
947                          WorkingCopy workingCopy,
948                          QStringList files,
949                          ModelManagerInterface *modelManager,
950                          Dialect mainLanguage,
951                          bool emitDocChangedOnDisk)
952 {
953     FutureReporter reporter(future);
954     future.setProgressRange(0, 100);
955 
956     // paths we have scanned for files and added to the files list
957     QSet<QString> scannedPaths;
958     // libraries we've found while scanning imports
959     QSet<QString> newLibraries;
960     parseLoop(scannedPaths, newLibraries, workingCopy, files, modelManager, mainLanguage,
961               emitDocChangedOnDisk, reporter);
962     future.setProgressValue(100);
963 }
964 
965 struct ScanItem {
966     QString path;
967     int depth;
968     Dialect language;
ScanItemQmlJS::ScanItem969     ScanItem(QString path = QString(), int depth = 0, Dialect language = Dialect::AnyLanguage)
970         : path(path), depth(depth), language(language)
971     { }
972 };
973 
importScan(QFutureInterface<void> & future,ModelManagerInterface::WorkingCopy workingCopy,PathsAndLanguages paths,ModelManagerInterface * modelManager,bool emitDocChangedOnDisk,bool libOnly,bool forceRescan)974 void ModelManagerInterface::importScan(QFutureInterface<void> &future,
975                               ModelManagerInterface::WorkingCopy workingCopy,
976                               PathsAndLanguages paths, ModelManagerInterface *modelManager,
977                               bool emitDocChangedOnDisk, bool libOnly, bool forceRescan)
978 {
979     // paths we have scanned for files and added to the files list
980     QSet<QString> scannedPaths;
981     {
982         QMutexLocker l(&modelManager->m_mutex);
983         scannedPaths = modelManager->m_scannedPaths;
984     }
985     // libraries we've found while scanning imports
986     QSet<QString> newLibraries;
987 
988     QVector<ScanItem> pathsToScan;
989     pathsToScan.reserve(paths.size());
990     {
991         QMutexLocker l(&modelManager->m_mutex);
992         for (int i = 0; i < paths.size(); ++i) {
993             PathAndLanguage pAndL = paths.at(i);
994             QString cPath = QDir::cleanPath(pAndL.path().toString());
995             if (!forceRescan && modelManager->m_scannedPaths.contains(cPath))
996                 continue;
997             pathsToScan.append(ScanItem(cPath, 0, pAndL.language()));
998             modelManager->m_scannedPaths.insert(cPath);
999         }
1000     }
1001     const int maxScanDepth = 5;
1002     int progressRange = pathsToScan.size() * (1 << (2 + maxScanDepth));
1003     int totalWork(progressRange), workDone(0);
1004     future.setProgressRange(0, progressRange); // update max length while iterating?
1005     const Snapshot snapshot = modelManager->snapshot();
1006     bool isCanceled = future.isCanceled();
1007     while (!pathsToScan.isEmpty() && !isCanceled) {
1008         ScanItem toScan = pathsToScan.last();
1009         pathsToScan.pop_back();
1010         int pathBudget = (1 << (maxScanDepth + 2 - toScan.depth));
1011         if (forceRescan || !scannedPaths.contains(toScan.path)) {
1012             QStringList importedFiles;
1013             if (forceRescan ||
1014                     (!findNewQmlLibraryInPath(toScan.path, snapshot, modelManager, &importedFiles,
1015                                          &scannedPaths, &newLibraries, true)
1016                     && !libOnly && snapshot.documentsInDirectory(toScan.path).isEmpty())) {
1017                 importedFiles += filesInDirectoryForLanguages(toScan.path,
1018                                                      toScan.language.companionLanguages());
1019             }
1020             workDone += 1;
1021             future.setProgressValue(progressRange * workDone / totalWork);
1022             if (!importedFiles.isEmpty()) {
1023                 FutureReporter reporter(future, progressRange * pathBudget / (4 * totalWork),
1024                                         progressRange * workDone / totalWork);
1025                 parseLoop(scannedPaths, newLibraries, workingCopy, importedFiles, modelManager,
1026                           toScan.language, emitDocChangedOnDisk, reporter); // run in parallel??
1027                 importedFiles.clear();
1028             }
1029             workDone += pathBudget / 4 - 1;
1030             future.setProgressValue(progressRange * workDone / totalWork);
1031         } else {
1032             workDone += pathBudget / 4;
1033         }
1034         // always descend tree, as we might have just scanned with a smaller depth
1035         if (toScan.depth < maxScanDepth) {
1036             QDir dir(toScan.path);
1037             QStringList subDirs(dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot));
1038             workDone += 1;
1039             totalWork += pathBudget / 2 * subDirs.size() - pathBudget * 3 / 4 + 1;
1040             foreach (const QString path, subDirs)
1041                 pathsToScan.append(ScanItem(dir.absoluteFilePath(path), toScan.depth + 1, toScan.language));
1042         } else {
1043             workDone += pathBudget * 3 / 4;
1044         }
1045         future.setProgressValue(progressRange * workDone / totalWork);
1046         isCanceled = future.isCanceled();
1047     }
1048     future.setProgressValue(progressRange);
1049     if (isCanceled) {
1050         // assume no work has been done
1051         QMutexLocker l(&modelManager->m_mutex);
1052         for (int i = 0; i < paths.size(); ++i)
1053             modelManager->m_scannedPaths.remove(paths.at(i).path().toString());
1054     }
1055 }
1056 
importPathsNames() const1057 QStringList ModelManagerInterface::importPathsNames() const
1058 {
1059     QStringList names;
1060     QMutexLocker l(&m_mutex);
1061     names.reserve(m_allImportPaths.size());
1062     for (const PathAndLanguage &x: m_allImportPaths)
1063         names << x.path().toString();
1064     return names;
1065 }
1066 
activeBundles() const1067 QmlLanguageBundles ModelManagerInterface::activeBundles() const
1068 {
1069     QMutexLocker l(&m_mutex);
1070     return m_activeBundles;
1071 }
1072 
extendedBundles() const1073 QmlLanguageBundles ModelManagerInterface::extendedBundles() const
1074 {
1075     QMutexLocker l(&m_mutex);
1076     return m_extendedBundles;
1077 }
1078 
maybeScan(const PathsAndLanguages & importPaths)1079 void ModelManagerInterface::maybeScan(const PathsAndLanguages &importPaths)
1080 {
1081     if (!m_indexerEnabled)
1082         return;
1083     PathsAndLanguages pathToScan;
1084     {
1085         QMutexLocker l(&m_mutex);
1086         foreach (const PathAndLanguage &importPath, importPaths)
1087             if (!m_scannedPaths.contains(importPath.path().toString()))
1088                 pathToScan.maybeInsert(importPath);
1089     }
1090 
1091     if (pathToScan.length() > 1) {
1092         QFuture<void> result = Utils::runAsync(&ModelManagerInterface::importScan,
1093                                                workingCopyInternal(), pathToScan,
1094                                                this, true, true, false);
1095         cleanupFutures();
1096         m_futures.append(result);
1097 
1098         addTaskInternal(result, tr("Scanning QML Imports"), Constants::TASK_IMPORT_SCAN);
1099     }
1100 }
1101 
updateImportPaths()1102 void ModelManagerInterface::updateImportPaths()
1103 {
1104     if (!m_indexerEnabled)
1105         return;
1106     PathsAndLanguages allImportPaths;
1107     QmlLanguageBundles activeBundles;
1108     QmlLanguageBundles extendedBundles;
1109     QMapIterator<ProjectExplorer::Project *, ProjectInfo> pInfoIter(m_projects);
1110     QHashIterator<Dialect, QmlJS::ViewerContext> vCtxsIter = m_defaultVContexts;
1111     while (pInfoIter.hasNext()) {
1112         pInfoIter.next();
1113         const PathsAndLanguages &iPaths = pInfoIter.value().importPaths;
1114         for (int i = 0; i < iPaths.size(); ++i) {
1115             PathAndLanguage pAndL = iPaths.at(i);
1116             const QString canonicalPath = pAndL.path().toFileInfo().canonicalFilePath();
1117             if (!canonicalPath.isEmpty())
1118                 allImportPaths.maybeInsert(Utils::FileName::fromString(canonicalPath),
1119                                            pAndL.language());
1120         }
1121     }
1122     while (vCtxsIter.hasNext()) {
1123         vCtxsIter.next();
1124         foreach (const QString &path, vCtxsIter.value().paths)
1125             allImportPaths.maybeInsert(Utils::FileName::fromString(path), vCtxsIter.value().language);
1126     }
1127     pInfoIter.toFront();
1128     while (pInfoIter.hasNext()) {
1129         pInfoIter.next();
1130         activeBundles.mergeLanguageBundles(pInfoIter.value().activeBundle);
1131         foreach (Dialect l, pInfoIter.value().activeBundle.languages()) {
1132             foreach (const QString &path, pInfoIter.value().activeBundle.bundleForLanguage(l)
1133                  .searchPaths().stringList()) {
1134                 const QString canonicalPath = QFileInfo(path).canonicalFilePath();
1135                 if (!canonicalPath.isEmpty())
1136                     allImportPaths.maybeInsert(Utils::FileName::fromString(canonicalPath), l);
1137             }
1138         }
1139     }
1140     pInfoIter.toFront();
1141     while (pInfoIter.hasNext()) {
1142         pInfoIter.next();
1143         QString pathAtt = pInfoIter.value().qtQmlPath;
1144         if (!pathAtt.isEmpty())
1145             allImportPaths.maybeInsert(Utils::FileName::fromString(pathAtt), Dialect::QmlQtQuick2);
1146     }
1147     {
1148         QString pathAtt = defaultProjectInfo().qtQmlPath;
1149         if (!pathAtt.isEmpty())
1150             allImportPaths.maybeInsert(Utils::FileName::fromString(pathAtt), Dialect::QmlQtQuick2);
1151     }
1152 
1153     foreach (const QString &path, m_defaultImportPaths)
1154         allImportPaths.maybeInsert(Utils::FileName::fromString(path), Dialect::Qml);
1155     allImportPaths.compact();
1156 
1157     {
1158         QMutexLocker l(&m_mutex);
1159         m_allImportPaths = allImportPaths;
1160         m_activeBundles = activeBundles;
1161         m_extendedBundles = extendedBundles;
1162     }
1163 
1164 
1165     // check if any file in the snapshot imports something new in the new paths
1166     Snapshot snapshot = m_validSnapshot;
1167     QStringList importedFiles;
1168     QSet<QString> scannedPaths;
1169     QSet<QString> newLibraries;
1170     foreach (const Document::Ptr &doc, snapshot)
1171         findNewLibraryImports(doc, snapshot, this, &importedFiles, &scannedPaths, &newLibraries);
1172 
1173     updateSourceFiles(importedFiles, true);
1174 
1175     if (!m_shouldScanImports)
1176         return;
1177     maybeScan(allImportPaths);
1178 }
1179 
loadPluginTypes(const QString & libraryPath,const QString & importPath,const QString & importUri,const QString & importVersion)1180 void ModelManagerInterface::loadPluginTypes(const QString &libraryPath, const QString &importPath,
1181                                    const QString &importUri, const QString &importVersion)
1182 {
1183     m_pluginDumper->loadPluginTypes(libraryPath, importPath, importUri, importVersion);
1184 }
1185 
1186 #if 0
1187 // is called *inside a c++ parsing thread*, to allow hanging on to source and ast
1188 void ModelManagerInterface::maybeQueueCppQmlTypeUpdate(const CPlusPlus::Document::Ptr &doc)
1189 {
1190     // avoid scanning documents without source code available
1191     doc->keepSourceAndAST();
1192     if (doc->utf8Source().isEmpty()) {
1193         doc->releaseSourceAndAST();
1194         return;
1195     }
1196 
1197     // keep source and AST alive if we want to scan for register calls
1198     const bool scan = FindExportedCppTypes::maybeExportsTypes(doc);
1199     if (!scan)
1200         doc->releaseSourceAndAST();
1201 
1202     // delegate actual queuing to the gui thread
1203     QMetaObject::invokeMethod(this, "queueCppQmlTypeUpdate",
1204                               Q_ARG(CPlusPlus::Document::Ptr, doc), Q_ARG(bool, scan));
1205 }
1206 
1207 void ModelManagerInterface::queueCppQmlTypeUpdate(const CPlusPlus::Document::Ptr &doc, bool scan)
1208 {
1209     QPair<CPlusPlus::Document::Ptr, bool> prev = m_queuedCppDocuments.value(doc->fileName());
1210     if (prev.first && prev.second)
1211         prev.first->releaseSourceAndAST();
1212     m_queuedCppDocuments.insert(doc->fileName(), {doc, scan});
1213     m_updateCppQmlTypesTimer->start();
1214 }
1215 
1216 void ModelManagerInterface::startCppQmlTypeUpdate()
1217 {
1218     // if a future is still running, delay
1219     if (m_cppQmlTypesUpdater.isRunning()) {
1220         m_updateCppQmlTypesTimer->start();
1221         return;
1222     }
1223 
1224     CPlusPlus::CppModelManagerBase *cppModelManager =
1225             CPlusPlus::CppModelManagerBase::instance();
1226     if (!cppModelManager)
1227         return;
1228 
1229     m_cppQmlTypesUpdater = Utils::runAsync(&ModelManagerInterface::updateCppQmlTypes,
1230                 this, cppModelManager->snapshot(), m_queuedCppDocuments);
1231     m_queuedCppDocuments.clear();
1232 }
1233 #endif
1234 
mutex() const1235 QMutex *ModelManagerInterface::mutex() const
1236 {
1237     return &m_mutex;
1238 }
1239 
asyncReset()1240 void ModelManagerInterface::asyncReset()
1241 {
1242     m_asyncResetTimer->start();
1243 }
1244 
1245 #if 0
1246 bool rescanExports(const QString &fileName, FindExportedCppTypes &finder,
1247                    ModelManagerInterface::CppDataHash &newData)
1248 {
1249     bool hasNewInfo = false;
1250 
1251     QList<LanguageUtils::FakeMetaObject::ConstPtr> exported = finder.exportedTypes();
1252     QHash<QString, QString> contextProperties = finder.contextProperties();
1253     if (exported.isEmpty() && contextProperties.isEmpty()) {
1254         hasNewInfo = hasNewInfo || newData.remove(fileName) > 0;
1255     } else {
1256         ModelManagerInterface::CppData &data = newData[fileName];
1257         if (!hasNewInfo && (data.exportedTypes.size() != exported.size()
1258                             || data.contextProperties != contextProperties))
1259             hasNewInfo = true;
1260         if (!hasNewInfo) {
1261             QHash<QString, QByteArray> newFingerprints;
1262             foreach (LanguageUtils::FakeMetaObject::ConstPtr newType, exported)
1263                 newFingerprints[newType->className()]=newType->fingerprint();
1264             foreach (LanguageUtils::FakeMetaObject::ConstPtr oldType, data.exportedTypes) {
1265                 if (newFingerprints.value(oldType->className()) != oldType->fingerprint()) {
1266                     hasNewInfo = true;
1267                     break;
1268                 }
1269             }
1270         }
1271         data.exportedTypes = exported;
1272         data.contextProperties = contextProperties;
1273     }
1274     return hasNewInfo;
1275 }
1276 
1277 void ModelManagerInterface::updateCppQmlTypes(QFutureInterface<void> &futureInterface,
1278                                      ModelManagerInterface *qmlModelManager,
1279                                      CPlusPlus::Snapshot snapshot,
1280                                      QHash<QString, QPair<CPlusPlus::Document::Ptr, bool> > documents)
1281 {
1282     futureInterface.setProgressRange(0, documents.size());
1283     futureInterface.setProgressValue(0);
1284 
1285     CppDataHash newData;
1286     QHash<QString, QList<CPlusPlus::Document::Ptr> > newDeclarations;
1287     {
1288         QMutexLocker locker(&qmlModelManager->m_cppDataMutex);
1289         newData = qmlModelManager->m_cppDataHash;
1290         newDeclarations = qmlModelManager->m_cppDeclarationFiles;
1291     }
1292 
1293     FindExportedCppTypes finder(snapshot);
1294 
1295     bool hasNewInfo = false;
1296     typedef QPair<CPlusPlus::Document::Ptr, bool> DocScanPair;
1297     foreach (const DocScanPair &pair, documents) {
1298         if (futureInterface.isCanceled())
1299             return;
1300         futureInterface.setProgressValue(futureInterface.progressValue() + 1);
1301 
1302         CPlusPlus::Document::Ptr doc = pair.first;
1303         const bool scan = pair.second;
1304         const QString fileName = doc->fileName();
1305         if (!scan) {
1306             hasNewInfo = newData.remove(fileName) > 0 || hasNewInfo;
1307             foreach (const CPlusPlus::Document::Ptr &savedDoc, newDeclarations.value(fileName)) {
1308                 finder(savedDoc);
1309                 hasNewInfo = rescanExports(savedDoc->fileName(), finder, newData) || hasNewInfo;
1310             }
1311             continue;
1312         }
1313 
1314         for (auto it = newDeclarations.begin(), end = newDeclarations.end(); it != end;) {
1315             for (auto docIt = it->begin(), endDocIt = it->end(); docIt != endDocIt;) {
1316                 CPlusPlus::Document::Ptr &savedDoc = *docIt;
1317                 if (savedDoc->fileName() == fileName) {
1318                     savedDoc->releaseSourceAndAST();
1319                     it->erase(docIt);
1320                     break;
1321                 } else {
1322                     ++docIt;
1323                 }
1324             }
1325             if (it->isEmpty())
1326                 it = newDeclarations.erase(it);
1327             else
1328                 ++it;
1329         }
1330 
1331         foreach (const QString &declarationFile, finder(doc)) {
1332             newDeclarations[declarationFile].append(doc);
1333             doc->keepSourceAndAST(); // keep for later reparsing when dependent doc changes
1334         }
1335 
1336         hasNewInfo = rescanExports(fileName, finder, newData) || hasNewInfo;
1337         doc->releaseSourceAndAST();
1338     }
1339 
1340     QMutexLocker locker(&qmlModelManager->m_cppDataMutex);
1341     qmlModelManager->m_cppDataHash = newData;
1342     qmlModelManager->m_cppDeclarationFiles = newDeclarations;
1343     if (hasNewInfo)
1344         // one could get away with re-linking the cpp types...
1345         QMetaObject::invokeMethod(qmlModelManager, "asyncReset");
1346 }
1347 #endif
1348 
cppData() const1349 ModelManagerInterface::CppDataHash ModelManagerInterface::cppData() const
1350 {
1351     QMutexLocker locker(&m_cppDataMutex);
1352     return m_cppDataHash;
1353 }
1354 
builtins(const Document::Ptr & doc) const1355 LibraryInfo ModelManagerInterface::builtins(const Document::Ptr &doc) const
1356 {
1357     ProjectInfo info = projectInfoForPath(doc->fileName());
1358     if (!info.isValid())
1359         return LibraryInfo();
1360     if (!info.qtQmlPath.isEmpty())
1361         return m_validSnapshot.libraryInfo(info.qtQmlPath);
1362     return m_validSnapshot.libraryInfo(info.qtImportsPath);
1363 }
1364 
completeVContext(const ViewerContext & vCtx,const Document::Ptr & doc) const1365 ViewerContext ModelManagerInterface::completeVContext(const ViewerContext &vCtx,
1366                                                       const Document::Ptr &doc) const
1367 {
1368     ViewerContext res = vCtx;
1369 
1370     if (!doc.isNull()
1371             && ((vCtx.language == Dialect::AnyLanguage && doc->language() != Dialect::NoLanguage)
1372                 || (vCtx.language == Dialect::Qml
1373                     && (doc->language() == Dialect::QmlQtQuick2
1374                         || doc->language() == Dialect::QmlQtQuick2Ui))))
1375         res.language = doc->language();
1376     ProjectInfo info;
1377     if (!doc.isNull())
1378         info = projectInfoForPath(doc->fileName());
1379     ViewerContext defaultVCtx = defaultVContext(res.language, Document::Ptr(0), false);
1380     ProjectInfo defaultInfo = defaultProjectInfo();
1381     if (info.qtImportsPath.isEmpty())
1382         info.qtImportsPath = defaultInfo.qtImportsPath;
1383     if (info.qtQmlPath.isEmpty())
1384         info.qtQmlPath = defaultInfo.qtQmlPath;
1385     switch (res.flags) {
1386     case ViewerContext::Complete:
1387         break;
1388     case ViewerContext::AddAllPathsAndDefaultSelectors:
1389         res.selectors.append(defaultVCtx.selectors);
1390         Q_FALLTHROUGH();
1391     case ViewerContext::AddAllPaths:
1392     {
1393         foreach (const QString &path, defaultVCtx.paths)
1394             res.maybeAddPath(path);
1395         switch (res.language.dialect()) {
1396         case Dialect::AnyLanguage:
1397         case Dialect::Qml:
1398             res.maybeAddPath(info.qtQmlPath);
1399             res.maybeAddPath(info.qtImportsPath);
1400             Q_FALLTHROUGH();
1401         case Dialect::QmlQtQuick2:
1402         case Dialect::QmlQtQuick2Ui:
1403         {
1404             if (res.language == Dialect::QmlQtQuick2 || res.language == Dialect::QmlQtQuick2Ui)
1405                 res.maybeAddPath(info.qtQmlPath);
1406             QList<ProjectInfo> allProjects;
1407             {
1408                 QMutexLocker locker(&m_mutex);
1409                 allProjects = m_projects.values();
1410             }
1411             std::sort(allProjects.begin(), allProjects.end(), &pInfoLessThanImports);
1412             QList<Dialect> languages = res.language.companionLanguages();
1413             foreach (const ProjectInfo &pInfo, allProjects) {
1414                 for (int i = 0; i< pInfo.importPaths.size(); ++i) {
1415                     PathAndLanguage pAndL = pInfo.importPaths.at(i);
1416                     if (languages.contains(pAndL.language()) || pAndL.language().companionLanguages().contains(res.language))
1417                         res.maybeAddPath(pAndL.path().toString());
1418                 }
1419             }
1420             foreach (const QString &path, environmentImportPaths())
1421                 res.maybeAddPath(path);
1422             break;
1423         }
1424         case Dialect::NoLanguage:
1425         case Dialect::JavaScript:
1426         case Dialect::QmlTypeInfo:
1427         case Dialect::Json:
1428         case Dialect::QmlQbs:
1429         case Dialect::QmlProject:
1430             break;
1431         }
1432         break;
1433     }
1434     case ViewerContext::AddDefaultPathsAndSelectors:
1435         res.selectors.append(defaultVCtx.selectors);
1436         Q_FALLTHROUGH();
1437     case ViewerContext::AddDefaultPaths:
1438         foreach (const QString &path, defaultVCtx.paths)
1439             res.maybeAddPath(path);
1440         if (res.language == Dialect::AnyLanguage || res.language == Dialect::Qml
1441                 || res.language == Dialect::QmlQtQuick2 || res.language == Dialect::QmlQtQuick2Ui)
1442             res.maybeAddPath(info.qtImportsPath);
1443         if (res.language == Dialect::AnyLanguage || res.language == Dialect::Qml)
1444             res.maybeAddPath(info.qtQmlPath);
1445         if (res.language == Dialect::AnyLanguage || res.language == Dialect::Qml
1446                 || res.language == Dialect::QmlQtQuick2 || res.language == Dialect::QmlQtQuick2Ui) {
1447             foreach (const QString &path, environmentImportPaths())
1448                 res.maybeAddPath(path);
1449         }
1450         break;
1451     }
1452     res.flags = ViewerContext::Complete;
1453     return res;
1454 }
1455 
defaultVContext(Dialect language,const Document::Ptr & doc,bool autoComplete) const1456 ViewerContext ModelManagerInterface::defaultVContext(Dialect language,
1457                                                      const Document::Ptr &doc,
1458                                                      bool autoComplete) const
1459 {
1460     if (!doc.isNull()) {
1461         if (language == Dialect::AnyLanguage && doc->language() != Dialect::NoLanguage)
1462             language = doc->language();
1463         else if (language == Dialect::Qml &&
1464                  (doc->language() == Dialect::QmlQtQuick2
1465                   || doc->language() == Dialect::QmlQtQuick2Ui))
1466             language = doc->language();
1467     }
1468     ViewerContext defaultCtx;
1469     {
1470         QMutexLocker locker(&m_mutex);
1471         defaultCtx = m_defaultVContexts.value(language);
1472     }
1473     defaultCtx.language = language;
1474     if (autoComplete)
1475         return completeVContext(defaultCtx, doc);
1476     else
1477         return defaultCtx;
1478 }
1479 
defaultProjectInfo() const1480 ModelManagerInterface::ProjectInfo ModelManagerInterface::defaultProjectInfo() const
1481 {
1482     QMutexLocker l(mutex());
1483     return m_defaultProjectInfo;
1484 }
1485 
defaultProjectInfoForProject(ProjectExplorer::Project *) const1486 ModelManagerInterface::ProjectInfo ModelManagerInterface::defaultProjectInfoForProject(
1487         ProjectExplorer::Project *) const
1488 {
1489     return ModelManagerInterface::ProjectInfo();
1490 }
1491 
setDefaultVContext(const ViewerContext & vContext)1492 void ModelManagerInterface::setDefaultVContext(const ViewerContext &vContext)
1493 {
1494     QMutexLocker locker(&m_mutex);
1495     m_defaultVContexts[vContext.language] = vContext;
1496 }
1497 
joinAllThreads()1498 void ModelManagerInterface::joinAllThreads()
1499 {
1500     foreach (QFuture<void> future, m_futures)
1501         future.waitForFinished();
1502     m_futures.clear();
1503 }
1504 
ensuredGetDocumentForPath(const QString & filePath)1505 Document::Ptr ModelManagerInterface::ensuredGetDocumentForPath(const QString &filePath)
1506 {
1507     QmlJS::Document::Ptr document = newestSnapshot().document(filePath);
1508     if (!document) {
1509         document = QmlJS::Document::create(filePath, QmlJS::Dialect::Qml);
1510         QMutexLocker lock(&m_mutex);
1511 
1512         m_newestSnapshot.insert(document);
1513     }
1514 
1515     return document;
1516 }
1517 
resetCodeModel()1518 void ModelManagerInterface::resetCodeModel()
1519 {
1520     QStringList documents;
1521 
1522     {
1523         QMutexLocker locker(&m_mutex);
1524 
1525         // find all documents currently in the code model
1526         foreach (Document::Ptr doc, m_validSnapshot)
1527             documents.append(doc->fileName());
1528 
1529         // reset the snapshot
1530         m_validSnapshot = Snapshot();
1531         m_newestSnapshot = Snapshot();
1532     }
1533 
1534     // start a reparse thread
1535     updateSourceFiles(documents, false);
1536 }
1537 
1538 } // namespace QmlJS
1539