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