forked from qt-creator/qt-creator
When rescanning imports we generally get a new version of the parent document. When replacing the cached documents we want to release the old ones, not the new ones. Failing to do so leads to types disappearing from the QML code model and to leaking memory. Task-number: QTCREATORBUG-17175 Change-Id: I3994444ac0a6cd87f9d9d0b47ab3d6015660e416 Reviewed-by: Thomas Hartmann <Thomas.Hartmann@theqtcompany.com>
1560 lines
55 KiB
C++
1560 lines
55 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
** Contact: https://www.qt.io/licensing/
|
|
**
|
|
** This file is part of Qt Creator.
|
|
**
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 3 as published by the Free Software
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
** included in the packaging of this file. Please review the following
|
|
** information to ensure the GNU General Public License requirements will
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include "qmljsbind.h"
|
|
#include "qmljsconstants.h"
|
|
#include "qmljsfindexportedcpptypes.h"
|
|
#include "qmljsinterpreter.h"
|
|
#include "qmljsmodelmanagerinterface.h"
|
|
#include "qmljsplugindumper.h"
|
|
#include "qmljstypedescriptionreader.h"
|
|
#include "qmljsdialect.h"
|
|
#include "qmljsviewercontext.h"
|
|
|
|
#include <cplusplus/cppmodelmanagerbase.h>
|
|
#include <utils/algorithm.h>
|
|
#include <utils/hostosinfo.h>
|
|
#include <utils/runextensions.h>
|
|
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QMetaObject>
|
|
#include <QRegExp>
|
|
#include <QTextDocument>
|
|
#include <QTextStream>
|
|
#include <QTimer>
|
|
#include <QtAlgorithms>
|
|
#include <QLibraryInfo>
|
|
|
|
#include <stdio.h>
|
|
|
|
namespace QmlJS {
|
|
|
|
QMLJS_EXPORT Q_LOGGING_CATEGORY(qmljsLog, "qtc.qmljs.common")
|
|
|
|
/*!
|
|
\class QmlJS::ModelManagerInterface
|
|
\brief The ModelManagerInterface class acts as an interface to the
|
|
global state of the QmlJS code model.
|
|
\sa QmlJS::Document QmlJS::Snapshot QmlJSTools::Internal::ModelManager
|
|
|
|
The ModelManagerInterface is an interface for global state and actions in
|
|
the QmlJS code model. It is implemented by \l{QmlJSTools::Internal::ModelManager}
|
|
and the instance can be accessed through ModelManagerInterface::instance().
|
|
|
|
One of its primary concerns is to keep the Snapshots it
|
|
maintains up to date by parsing documents and finding QML modules.
|
|
|
|
It has a Snapshot that contains only valid Documents,
|
|
accessible through ModelManagerInterface::snapshot() and a Snapshot with
|
|
potentially more recent, but invalid documents that is exposed through
|
|
ModelManagerInterface::newestSnapshot().
|
|
*/
|
|
|
|
static ModelManagerInterface *g_instance = 0;
|
|
|
|
const char qtQuickUISuffix[] = "ui.qml";
|
|
|
|
static QStringList environmentImportPaths()
|
|
{
|
|
QStringList paths;
|
|
|
|
QByteArray envImportPath = qgetenv("QML_IMPORT_PATH");
|
|
|
|
foreach (const QString &path, QString::fromLatin1(envImportPath)
|
|
.split(Utils::HostOsInfo::pathListSeparator(), QString::SkipEmptyParts)) {
|
|
QString canonicalPath = QDir(path).canonicalPath();
|
|
if (!canonicalPath.isEmpty() && !paths.contains(canonicalPath))
|
|
paths.append(canonicalPath);
|
|
}
|
|
|
|
return paths;
|
|
}
|
|
|
|
ModelManagerInterface::ModelManagerInterface(QObject *parent)
|
|
: QObject(parent),
|
|
m_shouldScanImports(false),
|
|
m_defaultProject(0),
|
|
m_pluginDumper(new PluginDumper(this))
|
|
{
|
|
m_indexerEnabled = qgetenv("QTC_NO_CODE_INDEXER") != "1";
|
|
|
|
m_updateCppQmlTypesTimer = new QTimer(this);
|
|
m_updateCppQmlTypesTimer->setInterval(1000);
|
|
m_updateCppQmlTypesTimer->setSingleShot(true);
|
|
connect(m_updateCppQmlTypesTimer, &QTimer::timeout,
|
|
this, &ModelManagerInterface::startCppQmlTypeUpdate);
|
|
|
|
m_asyncResetTimer = new QTimer(this);
|
|
m_asyncResetTimer->setInterval(15000);
|
|
m_asyncResetTimer->setSingleShot(true);
|
|
connect(m_asyncResetTimer, &QTimer::timeout, this, &ModelManagerInterface::resetCodeModel);
|
|
|
|
qRegisterMetaType<QmlJS::Document::Ptr>("QmlJS::Document::Ptr");
|
|
qRegisterMetaType<QmlJS::LibraryInfo>("QmlJS::LibraryInfo");
|
|
qRegisterMetaType<QmlJS::Dialect>("QmlJS::Dialect");
|
|
qRegisterMetaType<QmlJS::PathAndLanguage>("QmlJS::PathAndLanguage");
|
|
qRegisterMetaType<QmlJS::PathsAndLanguages>("QmlJS::PathsAndLanguages");
|
|
|
|
m_defaultProjectInfo.qtImportsPath = QFileInfo(
|
|
QLibraryInfo::location(QLibraryInfo::ImportsPath)).canonicalFilePath();
|
|
m_defaultProjectInfo.qtQmlPath = QFileInfo(
|
|
QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath)).canonicalFilePath();
|
|
|
|
m_defaultImportPaths << environmentImportPaths();
|
|
updateImportPaths();
|
|
|
|
Q_ASSERT(! g_instance);
|
|
g_instance = this;
|
|
}
|
|
|
|
ModelManagerInterface::~ModelManagerInterface()
|
|
{
|
|
m_cppQmlTypesUpdater.cancel();
|
|
m_cppQmlTypesUpdater.waitForFinished();
|
|
Q_ASSERT(g_instance == this);
|
|
g_instance = 0;
|
|
}
|
|
|
|
static QHash<QString, Dialect> defaultLanguageMapping()
|
|
{
|
|
static QHash<QString, Dialect> res{
|
|
{QLatin1String("js"), Dialect::JavaScript},
|
|
{QLatin1String("qml"), Dialect::Qml},
|
|
{QLatin1String("qmltypes"), Dialect::QmlTypeInfo},
|
|
{QLatin1String("qmlproject"), Dialect::QmlProject},
|
|
{QLatin1String("json"), Dialect::Json},
|
|
{QLatin1String("qbs"), Dialect::QmlQbs},
|
|
{QLatin1String(qtQuickUISuffix), Dialect::QmlQtQuick2Ui}
|
|
};
|
|
return res;
|
|
}
|
|
|
|
Dialect ModelManagerInterface::guessLanguageOfFile(const QString &fileName)
|
|
{
|
|
QHash<QString, Dialect> lMapping;
|
|
if (instance())
|
|
lMapping = instance()->languageForSuffix();
|
|
else
|
|
lMapping = defaultLanguageMapping();
|
|
const QFileInfo info(fileName);
|
|
QString fileSuffix = info.suffix();
|
|
|
|
/*
|
|
* I was reluctant to use complete suffix in all cases, because it is a huge
|
|
* change in behaivour. But in case of .qml this should be safe.
|
|
*/
|
|
|
|
if (fileSuffix == QLatin1String("qml"))
|
|
fileSuffix = info.completeSuffix();
|
|
|
|
return lMapping.value(fileSuffix, Dialect::NoLanguage);
|
|
}
|
|
|
|
QStringList ModelManagerInterface::globPatternsForLanguages(const QList<Dialect> languages)
|
|
{
|
|
QHash<QString, Dialect> lMapping;
|
|
if (instance())
|
|
lMapping = instance()->languageForSuffix();
|
|
else
|
|
lMapping = defaultLanguageMapping();
|
|
QStringList patterns;
|
|
QHashIterator<QString,Dialect> i(lMapping);
|
|
while (i.hasNext()) {
|
|
i.next();
|
|
if (languages.contains(i.value()))
|
|
patterns << QLatin1String("*.") + i.key();
|
|
}
|
|
return patterns;
|
|
}
|
|
|
|
ModelManagerInterface *ModelManagerInterface::instance()
|
|
{
|
|
return g_instance;
|
|
}
|
|
|
|
void ModelManagerInterface::writeWarning(const QString &msg)
|
|
{
|
|
if (ModelManagerInterface *i = instance())
|
|
i->writeMessageInternal(msg);
|
|
else
|
|
qCWarning(qmljsLog) << msg;
|
|
}
|
|
|
|
ModelManagerInterface::WorkingCopy ModelManagerInterface::workingCopy()
|
|
{
|
|
if (ModelManagerInterface *i = instance())
|
|
return i->workingCopyInternal();
|
|
return WorkingCopy();
|
|
}
|
|
|
|
void ModelManagerInterface::activateScan()
|
|
{
|
|
if (!m_shouldScanImports) {
|
|
m_shouldScanImports = true;
|
|
updateImportPaths();
|
|
}
|
|
}
|
|
|
|
QHash<QString, Dialect> ModelManagerInterface::languageForSuffix() const
|
|
{
|
|
return defaultLanguageMapping();
|
|
}
|
|
|
|
void ModelManagerInterface::writeMessageInternal(const QString &msg) const
|
|
{
|
|
qCDebug(qmljsLog) << msg;
|
|
}
|
|
|
|
ModelManagerInterface::WorkingCopy ModelManagerInterface::workingCopyInternal() const
|
|
{
|
|
ModelManagerInterface::WorkingCopy res;
|
|
return res;
|
|
}
|
|
|
|
void ModelManagerInterface::addTaskInternal(QFuture<void> result, const QString &msg,
|
|
const char *taskId) const
|
|
{
|
|
Q_UNUSED(result);
|
|
qCDebug(qmljsLog) << "started " << taskId << " " << msg;
|
|
}
|
|
|
|
void ModelManagerInterface::loadQmlTypeDescriptionsInternal(const QString &resourcePath)
|
|
{
|
|
const QDir typeFileDir(resourcePath + QLatin1String("/qml-type-descriptions"));
|
|
const QStringList qmlTypesExtensions = QStringList() << QLatin1String("*.qmltypes");
|
|
QFileInfoList qmlTypesFiles = typeFileDir.entryInfoList(
|
|
qmlTypesExtensions,
|
|
QDir::Files,
|
|
QDir::Name);
|
|
|
|
QStringList errors;
|
|
QStringList warnings;
|
|
|
|
// filter out the actual Qt builtins
|
|
for (int i = 0; i < qmlTypesFiles.size(); ++i) {
|
|
if (qmlTypesFiles.at(i).baseName() == QLatin1String("builtins")) {
|
|
QFileInfoList list;
|
|
list.append(qmlTypesFiles.at(i));
|
|
CppQmlTypesLoader::defaultQtObjects =
|
|
CppQmlTypesLoader::loadQmlTypes(list, &errors, &warnings);
|
|
qmlTypesFiles.removeAt(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// load the fallbacks for libraries
|
|
CppQmlTypesLoader::defaultLibraryObjects.unite(
|
|
CppQmlTypesLoader::loadQmlTypes(qmlTypesFiles, &errors, &warnings));
|
|
|
|
foreach (const QString &error, errors)
|
|
writeMessageInternal(error);
|
|
foreach (const QString &warning, warnings)
|
|
writeMessageInternal(warning);
|
|
}
|
|
|
|
void ModelManagerInterface::setDefaultProject(const ModelManagerInterface::ProjectInfo &pInfo,
|
|
ProjectExplorer::Project *p)
|
|
{
|
|
QMutexLocker l(mutex());
|
|
m_defaultProject = p;
|
|
m_defaultProjectInfo = pInfo;
|
|
}
|
|
|
|
Snapshot ModelManagerInterface::snapshot() const
|
|
{
|
|
QMutexLocker locker(&m_mutex);
|
|
return m_validSnapshot;
|
|
}
|
|
|
|
Snapshot ModelManagerInterface::newestSnapshot() const
|
|
{
|
|
QMutexLocker locker(&m_mutex);
|
|
return m_newestSnapshot;
|
|
}
|
|
|
|
void ModelManagerInterface::updateSourceFiles(const QStringList &files,
|
|
bool emitDocumentOnDiskChanged)
|
|
{
|
|
if (!m_indexerEnabled)
|
|
return;
|
|
refreshSourceFiles(files, emitDocumentOnDiskChanged);
|
|
}
|
|
|
|
void ModelManagerInterface::cleanupFutures()
|
|
{
|
|
if (m_futures.size() > 10) {
|
|
QList<QFuture<void> > futures = m_futures;
|
|
m_futures.clear();
|
|
foreach (const QFuture<void> &future, futures) {
|
|
if (!(future.isFinished() || future.isCanceled()))
|
|
m_futures.append(future);
|
|
}
|
|
}
|
|
}
|
|
|
|
QFuture<void> ModelManagerInterface::refreshSourceFiles(const QStringList &sourceFiles,
|
|
bool emitDocumentOnDiskChanged)
|
|
{
|
|
if (sourceFiles.isEmpty())
|
|
return QFuture<void>();
|
|
|
|
QFuture<void> result = Utils::runAsync(&ModelManagerInterface::parse,
|
|
workingCopyInternal(), sourceFiles,
|
|
this, Dialect(Dialect::Qml),
|
|
emitDocumentOnDiskChanged);
|
|
cleanupFutures();
|
|
m_futures.append(result);
|
|
|
|
if (sourceFiles.count() > 1)
|
|
addTaskInternal(result, tr("Parsing QML Files"), Constants::TASK_INDEX);
|
|
|
|
if (sourceFiles.count() > 1 && !m_shouldScanImports) {
|
|
bool scan = false;
|
|
{
|
|
QMutexLocker l(&m_mutex);
|
|
if (!m_shouldScanImports) {
|
|
m_shouldScanImports = true;
|
|
scan = true;
|
|
}
|
|
}
|
|
if (scan)
|
|
updateImportPaths();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
void ModelManagerInterface::fileChangedOnDisk(const QString &path)
|
|
{
|
|
Utils::runAsync(&ModelManagerInterface::parse,
|
|
workingCopyInternal(), QStringList() << path,
|
|
this, Dialect(Dialect::AnyLanguage), true);
|
|
}
|
|
|
|
void ModelManagerInterface::removeFiles(const QStringList &files)
|
|
{
|
|
emit aboutToRemoveFiles(files);
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
foreach (const QString &file, files) {
|
|
m_validSnapshot.remove(file);
|
|
m_newestSnapshot.remove(file);
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
bool pInfoLessThanActive(const ModelManagerInterface::ProjectInfo &p1, const ModelManagerInterface::ProjectInfo &p2)
|
|
{
|
|
QStringList s1 = p1.activeResourceFiles;
|
|
QStringList s2 = p2.activeResourceFiles;
|
|
if (s1.size() < s2.size())
|
|
return true;
|
|
if (s1.size() > s2.size())
|
|
return false;
|
|
for (int i = 0; i < s1.size(); ++i) {
|
|
if (s1.at(i) < s2.at(i))
|
|
return true;
|
|
else if (s1.at(i) > s2.at(i))
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool pInfoLessThanAll(const ModelManagerInterface::ProjectInfo &p1, const ModelManagerInterface::ProjectInfo &p2)
|
|
{
|
|
QStringList s1 = p1.allResourceFiles;
|
|
QStringList s2 = p2.allResourceFiles;
|
|
if (s1.size() < s2.size())
|
|
return true;
|
|
if (s1.size() > s2.size())
|
|
return false;
|
|
for (int i = 0; i < s1.size(); ++i) {
|
|
if (s1.at(i) < s2.at(i))
|
|
return true;
|
|
else if (s1.at(i) > s2.at(i))
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool pInfoLessThanImports(const ModelManagerInterface::ProjectInfo &p1, const ModelManagerInterface::ProjectInfo &p2)
|
|
{
|
|
if (p1.qtQmlPath < p2.qtQmlPath)
|
|
return true;
|
|
if (p1.qtQmlPath > p2.qtQmlPath)
|
|
return false;
|
|
if (p1.qtImportsPath < p2.qtImportsPath)
|
|
return true;
|
|
if (p1.qtImportsPath > p2.qtImportsPath)
|
|
return false;
|
|
const PathsAndLanguages &s1 = p1.importPaths;
|
|
const PathsAndLanguages &s2 = p2.importPaths;
|
|
if (s1.size() < s2.size())
|
|
return true;
|
|
if (s1.size() > s2.size())
|
|
return false;
|
|
for (int i = 0; i < s1.size(); ++i) {
|
|
if (s1.at(i) < s2.at(i))
|
|
return true;
|
|
else if (s2.at(i) < s1.at(i))
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
void ModelManagerInterface::iterateQrcFiles(ProjectExplorer::Project *project,
|
|
QrcResourceSelector resources,
|
|
std::function<void(QrcParser::ConstPtr)> callback)
|
|
{
|
|
QList<ProjectInfo> pInfos;
|
|
if (project) {
|
|
pInfos.append(projectInfo(project));
|
|
} else {
|
|
pInfos = projectInfos();
|
|
if (resources == ActiveQrcResources) // make the result predictable
|
|
Utils::sort(pInfos, &pInfoLessThanActive);
|
|
else
|
|
Utils::sort(pInfos, &pInfoLessThanAll);
|
|
}
|
|
|
|
QSet<QString> pathsChecked;
|
|
foreach (const ModelManagerInterface::ProjectInfo &pInfo, pInfos) {
|
|
QStringList qrcFilePaths;
|
|
if (resources == ActiveQrcResources)
|
|
qrcFilePaths = pInfo.activeResourceFiles;
|
|
else
|
|
qrcFilePaths = pInfo.allResourceFiles;
|
|
foreach (const QString &qrcFilePath, qrcFilePaths) {
|
|
if (pathsChecked.contains(qrcFilePath))
|
|
continue;
|
|
pathsChecked.insert(qrcFilePath);
|
|
QrcParser::ConstPtr qrcFile = m_qrcCache.parsedPath(qrcFilePath);
|
|
if (qrcFile.isNull())
|
|
continue;
|
|
callback(qrcFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
QStringList ModelManagerInterface::qrcPathsForFile(const QString &file, const QLocale *locale,
|
|
ProjectExplorer::Project *project,
|
|
QrcResourceSelector resources)
|
|
{
|
|
QStringList res;
|
|
iterateQrcFiles(project, resources, [&](QrcParser::ConstPtr qrcFile) {
|
|
qrcFile->collectResourceFilesForSourceFile(file, &res, locale);
|
|
});
|
|
return res;
|
|
}
|
|
|
|
QStringList ModelManagerInterface::filesAtQrcPath(const QString &path, const QLocale *locale,
|
|
ProjectExplorer::Project *project,
|
|
QrcResourceSelector resources)
|
|
{
|
|
QString normPath = QrcParser::normalizedQrcFilePath(path);
|
|
QStringList res;
|
|
iterateQrcFiles(project, resources, [&](QrcParser::ConstPtr qrcFile) {
|
|
qrcFile->collectFilesAtPath(normPath, &res, locale);
|
|
});
|
|
return res;
|
|
}
|
|
|
|
QMap<QString, QStringList> ModelManagerInterface::filesInQrcPath(const QString &path,
|
|
const QLocale *locale,
|
|
ProjectExplorer::Project *project,
|
|
bool addDirs,
|
|
QrcResourceSelector resources)
|
|
{
|
|
QString normPath = QrcParser::normalizedQrcDirectoryPath(path);
|
|
QMap<QString, QStringList> res;
|
|
iterateQrcFiles(project, resources, [&](QrcParser::ConstPtr qrcFile) {
|
|
qrcFile->collectFilesInPath(normPath, &res, addDirs, locale);
|
|
});
|
|
return res;
|
|
}
|
|
|
|
QList<ModelManagerInterface::ProjectInfo> ModelManagerInterface::projectInfos() const
|
|
{
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
return m_projects.values();
|
|
}
|
|
|
|
ModelManagerInterface::ProjectInfo ModelManagerInterface::projectInfo(
|
|
ProjectExplorer::Project *project,
|
|
const ModelManagerInterface::ProjectInfo &defaultValue) const
|
|
{
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
return m_projects.value(project, defaultValue);
|
|
}
|
|
|
|
void ModelManagerInterface::updateProjectInfo(const ProjectInfo &pinfo, ProjectExplorer::Project *p)
|
|
{
|
|
if (! pinfo.isValid() || !p || !m_indexerEnabled)
|
|
return;
|
|
|
|
Snapshot snapshot;
|
|
ProjectInfo oldInfo;
|
|
{
|
|
QMutexLocker locker(&m_mutex);
|
|
oldInfo = m_projects.value(p);
|
|
m_projects.insert(p, pinfo);
|
|
if (p == m_defaultProject)
|
|
m_defaultProjectInfo = pinfo;
|
|
snapshot = m_validSnapshot;
|
|
}
|
|
|
|
if (oldInfo.qmlDumpPath != pinfo.qmlDumpPath
|
|
|| oldInfo.qmlDumpEnvironment != pinfo.qmlDumpEnvironment) {
|
|
m_pluginDumper->scheduleRedumpPlugins();
|
|
m_pluginDumper->scheduleMaybeRedumpBuiltins(pinfo);
|
|
}
|
|
|
|
|
|
updateImportPaths();
|
|
|
|
// remove files that are no longer in the project and have been deleted
|
|
QStringList deletedFiles;
|
|
foreach (const QString &oldFile, oldInfo.sourceFiles) {
|
|
if (snapshot.document(oldFile)
|
|
&& !pinfo.sourceFiles.contains(oldFile)
|
|
&& !QFile::exists(oldFile)) {
|
|
deletedFiles += oldFile;
|
|
}
|
|
}
|
|
removeFiles(deletedFiles);
|
|
foreach (const QString &oldFile, deletedFiles)
|
|
m_fileToProject.remove(oldFile, p);
|
|
|
|
// parse any files not yet in the snapshot
|
|
QStringList newFiles;
|
|
foreach (const QString &file, pinfo.sourceFiles) {
|
|
if (!snapshot.document(file))
|
|
newFiles += file;
|
|
}
|
|
foreach (const QString &newFile, newFiles)
|
|
m_fileToProject.insert(newFile, p);
|
|
updateSourceFiles(newFiles, false);
|
|
|
|
// update qrc cache
|
|
foreach (const QString &newQrc, pinfo.allResourceFiles)
|
|
m_qrcCache.addPath(newQrc);
|
|
foreach (const QString &oldQrc, oldInfo.allResourceFiles)
|
|
m_qrcCache.removePath(oldQrc);
|
|
|
|
int majorVersion, minorVersion, patchVersion;
|
|
// dump builtin types if the shipped definitions are probably outdated and the
|
|
// Qt version ships qmlplugindump
|
|
if (::sscanf(pinfo.qtVersionString.toLatin1().constData(), "%d.%d.%d",
|
|
&majorVersion, &minorVersion, &patchVersion) != 3)
|
|
majorVersion = minorVersion = patchVersion = -1;
|
|
|
|
if (majorVersion > 4 || (majorVersion == 4 && (minorVersion > 8 || (majorVersion == 8
|
|
&& patchVersion >= 5)))) {
|
|
m_pluginDumper->loadBuiltinTypes(pinfo);
|
|
}
|
|
|
|
emit projectInfoUpdated(pinfo);
|
|
}
|
|
|
|
|
|
void ModelManagerInterface::removeProjectInfo(ProjectExplorer::Project *project)
|
|
{
|
|
ProjectInfo info;
|
|
info.sourceFiles.clear();
|
|
// update with an empty project info to clear data
|
|
updateProjectInfo(info, project);
|
|
|
|
{
|
|
QMutexLocker locker(&m_mutex);
|
|
m_projects.remove(project);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
Returns project info with summarized info for \a path
|
|
|
|
\note Project pointer will be empty
|
|
*/
|
|
ModelManagerInterface::ProjectInfo ModelManagerInterface::projectInfoForPath(const QString &path) const
|
|
{
|
|
QList<ProjectInfo> infos = allProjectInfosForPath(path);
|
|
|
|
ProjectInfo res;
|
|
foreach (const ProjectInfo &pInfo, infos) {
|
|
if (res.qtImportsPath.isEmpty())
|
|
res.qtImportsPath = pInfo.qtImportsPath;
|
|
if (res.qtQmlPath.isEmpty())
|
|
res.qtQmlPath = pInfo.qtQmlPath;
|
|
for (int i = 0; i < pInfo.importPaths.size(); ++i)
|
|
res.importPaths.maybeInsert(pInfo.importPaths.at(i));
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/*!
|
|
Returns list of project infos for \a path
|
|
*/
|
|
QList<ModelManagerInterface::ProjectInfo> ModelManagerInterface::allProjectInfosForPath(const QString &path) const
|
|
{
|
|
QList<ProjectExplorer::Project *> projects;
|
|
{
|
|
QMutexLocker locker(&m_mutex);
|
|
projects = m_fileToProject.values(path);
|
|
if (projects.isEmpty()) {
|
|
QFileInfo fInfo(path);
|
|
projects = m_fileToProject.values(fInfo.canonicalFilePath());
|
|
}
|
|
}
|
|
QList<ProjectInfo> infos;
|
|
foreach (ProjectExplorer::Project *project, projects) {
|
|
ProjectInfo info = projectInfo(project);
|
|
if (info.isValid())
|
|
infos.append(info);
|
|
}
|
|
std::sort(infos.begin(), infos.end(), &pInfoLessThanImports);
|
|
infos.append(m_defaultProjectInfo);
|
|
return infos;
|
|
}
|
|
|
|
bool ModelManagerInterface::isIdle() const
|
|
{
|
|
return m_futures.isEmpty();
|
|
}
|
|
|
|
void ModelManagerInterface::emitDocumentChangedOnDisk(Document::Ptr doc)
|
|
{ emit documentChangedOnDisk(doc); }
|
|
|
|
void ModelManagerInterface::updateQrcFile(const QString &path)
|
|
{
|
|
m_qrcCache.updatePath(path);
|
|
}
|
|
|
|
void ModelManagerInterface::updateDocument(Document::Ptr doc)
|
|
{
|
|
{
|
|
QMutexLocker locker(&m_mutex);
|
|
m_validSnapshot.insert(doc);
|
|
m_newestSnapshot.insert(doc, true);
|
|
}
|
|
emit documentUpdated(doc);
|
|
}
|
|
|
|
void ModelManagerInterface::updateLibraryInfo(const QString &path, const LibraryInfo &info)
|
|
{
|
|
if (!info.pluginTypeInfoError().isEmpty())
|
|
qCDebug(qmljsLog) << "Dumping errors for " << path << ":" << info.pluginTypeInfoError();
|
|
|
|
{
|
|
QMutexLocker locker(&m_mutex);
|
|
m_validSnapshot.insertLibraryInfo(path, info);
|
|
m_newestSnapshot.insertLibraryInfo(path, info);
|
|
}
|
|
// only emit if we got new useful information
|
|
if (info.isValid())
|
|
emit libraryInfoUpdated(path, info);
|
|
}
|
|
|
|
static QStringList filesInDirectoryForLanguages(const QString &path, QList<Dialect> languages)
|
|
{
|
|
const QStringList pattern = ModelManagerInterface::globPatternsForLanguages(languages);
|
|
QStringList files;
|
|
|
|
const QDir dir(path);
|
|
foreach (const QFileInfo &fi, dir.entryInfoList(pattern, QDir::Files))
|
|
files += fi.absoluteFilePath();
|
|
|
|
return files;
|
|
}
|
|
|
|
static void findNewImplicitImports(const Document::Ptr &doc, const Snapshot &snapshot,
|
|
QStringList *importedFiles, QSet<QString> *scannedPaths)
|
|
{
|
|
// scan files that could be implicitly imported
|
|
// it's important we also do this for JS files, otherwise the isEmpty check will fail
|
|
if (snapshot.documentsInDirectory(doc->path()).isEmpty()) {
|
|
if (! scannedPaths->contains(doc->path())) {
|
|
*importedFiles += filesInDirectoryForLanguages(doc->path(),
|
|
doc->language().companionLanguages());
|
|
scannedPaths->insert(doc->path());
|
|
}
|
|
}
|
|
}
|
|
|
|
static void findNewFileImports(const Document::Ptr &doc, const Snapshot &snapshot,
|
|
QStringList *importedFiles, QSet<QString> *scannedPaths)
|
|
{
|
|
// scan files and directories that are explicitly imported
|
|
foreach (const ImportInfo &import, doc->bind()->imports()) {
|
|
const QString &importName = import.path();
|
|
if (import.type() == ImportType::File) {
|
|
if (! snapshot.document(importName))
|
|
*importedFiles += importName;
|
|
} else if (import.type() == ImportType::Directory) {
|
|
if (snapshot.documentsInDirectory(importName).isEmpty()) {
|
|
if (! scannedPaths->contains(importName)) {
|
|
*importedFiles += filesInDirectoryForLanguages(importName,
|
|
doc->language().companionLanguages());
|
|
scannedPaths->insert(importName);
|
|
}
|
|
}
|
|
} else if (import.type() == ImportType::QrcFile) {
|
|
QStringList importPaths = ModelManagerInterface::instance()->filesAtQrcPath(importName);
|
|
foreach (const QString &importPath, importPaths) {
|
|
if (! snapshot.document(importPath))
|
|
*importedFiles += importPath;
|
|
}
|
|
} else if (import.type() == ImportType::QrcDirectory) {
|
|
QMapIterator<QString,QStringList> dirContents(ModelManagerInterface::instance()->filesInQrcPath(importName));
|
|
while (dirContents.hasNext()) {
|
|
dirContents.next();
|
|
if (ModelManagerInterface::guessLanguageOfFile(dirContents.key()).isQmlLikeOrJsLanguage()) {
|
|
foreach (const QString &filePath, dirContents.value()) {
|
|
if (! snapshot.document(filePath))
|
|
*importedFiles += filePath;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool findNewQmlLibraryInPath(const QString &path,
|
|
const Snapshot &snapshot,
|
|
ModelManagerInterface *modelManager,
|
|
QStringList *importedFiles,
|
|
QSet<QString> *scannedPaths,
|
|
QSet<QString> *newLibraries,
|
|
bool ignoreMissing)
|
|
{
|
|
// if we know there is a library, done
|
|
const LibraryInfo &existingInfo = snapshot.libraryInfo(path);
|
|
if (existingInfo.isValid())
|
|
return true;
|
|
if (newLibraries->contains(path))
|
|
return true;
|
|
// if we looked at the path before, done
|
|
if (existingInfo.wasScanned())
|
|
return false;
|
|
|
|
const QDir dir(path);
|
|
QFile qmldirFile(dir.filePath(QLatin1String("qmldir")));
|
|
if (!qmldirFile.exists()) {
|
|
if (!ignoreMissing) {
|
|
LibraryInfo libraryInfo(LibraryInfo::NotFound);
|
|
modelManager->updateLibraryInfo(path, libraryInfo);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (Utils::HostOsInfo::isWindowsHost()) {
|
|
// QTCREATORBUG-3402 - be case sensitive even here?
|
|
}
|
|
|
|
// found a new library!
|
|
qmldirFile.open(QFile::ReadOnly);
|
|
QString qmldirData = QString::fromUtf8(qmldirFile.readAll());
|
|
|
|
QmlDirParser qmldirParser;
|
|
qmldirParser.parse(qmldirData);
|
|
|
|
const QString libraryPath = QFileInfo(qmldirFile).absolutePath();
|
|
newLibraries->insert(libraryPath);
|
|
modelManager->updateLibraryInfo(libraryPath, LibraryInfo(qmldirParser));
|
|
modelManager->loadPluginTypes(QFileInfo(libraryPath).canonicalFilePath(), libraryPath,
|
|
QString(), QString());
|
|
|
|
// scan the qml files in the library
|
|
foreach (const QmlDirParser::Component &component, qmldirParser.components()) {
|
|
if (! component.fileName.isEmpty()) {
|
|
const QFileInfo componentFileInfo(dir.filePath(component.fileName));
|
|
const QString path = QDir::cleanPath(componentFileInfo.absolutePath());
|
|
if (! scannedPaths->contains(path)) {
|
|
*importedFiles += filesInDirectoryForLanguages(path,
|
|
Dialect(Dialect::AnyLanguage).companionLanguages());
|
|
scannedPaths->insert(path);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void findNewQmlLibrary(
|
|
const QString &path,
|
|
const LanguageUtils::ComponentVersion &version,
|
|
const Snapshot &snapshot,
|
|
ModelManagerInterface *modelManager,
|
|
QStringList *importedFiles,
|
|
QSet<QString> *scannedPaths,
|
|
QSet<QString> *newLibraries)
|
|
{
|
|
QString libraryPath = QString::fromLatin1("%1.%2.%3").arg(
|
|
path,
|
|
QString::number(version.majorVersion()),
|
|
QString::number(version.minorVersion()));
|
|
findNewQmlLibraryInPath(
|
|
libraryPath, snapshot, modelManager,
|
|
importedFiles, scannedPaths, newLibraries, false);
|
|
|
|
libraryPath = QString::fromLatin1("%1.%2").arg(
|
|
path,
|
|
QString::number(version.majorVersion()));
|
|
findNewQmlLibraryInPath(
|
|
libraryPath, snapshot, modelManager,
|
|
importedFiles, scannedPaths, newLibraries, false);
|
|
|
|
findNewQmlLibraryInPath(
|
|
path, snapshot, modelManager,
|
|
importedFiles, scannedPaths, newLibraries, false);
|
|
}
|
|
|
|
static void findNewLibraryImports(const Document::Ptr &doc, const Snapshot &snapshot,
|
|
ModelManagerInterface *modelManager,
|
|
QStringList *importedFiles, QSet<QString> *scannedPaths, QSet<QString> *newLibraries)
|
|
{
|
|
// scan current dir
|
|
findNewQmlLibraryInPath(doc->path(), snapshot, modelManager,
|
|
importedFiles, scannedPaths, newLibraries, false);
|
|
|
|
// scan dir and lib imports
|
|
const PathsAndLanguages importPaths = modelManager->importPaths();
|
|
foreach (const ImportInfo &import, doc->bind()->imports()) {
|
|
if (import.type() == ImportType::Directory) {
|
|
const QString targetPath = import.path();
|
|
findNewQmlLibraryInPath(targetPath, snapshot, modelManager,
|
|
importedFiles, scannedPaths, newLibraries, false);
|
|
}
|
|
|
|
if (import.type() == ImportType::Library) {
|
|
if (!import.version().isValid())
|
|
continue;
|
|
foreach (const PathAndLanguage &importPath, importPaths) {
|
|
const QString targetPath = importPath.path().appendPath(import.path()).toString();
|
|
findNewQmlLibrary(targetPath, import.version(), snapshot, modelManager,
|
|
importedFiles, scannedPaths, newLibraries);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ModelManagerInterface::parseLoop(QSet<QString> &scannedPaths,
|
|
QSet<QString> &newLibraries,
|
|
WorkingCopy workingCopy,
|
|
QStringList files,
|
|
ModelManagerInterface *modelManager,
|
|
Dialect mainLanguage,
|
|
bool emitDocChangedOnDisk,
|
|
std::function<bool(qreal)> reportProgress)
|
|
{
|
|
for (int i = 0; i < files.size(); ++i) {
|
|
if (!reportProgress(qreal(i) / files.size()))
|
|
return;
|
|
|
|
const QString fileName = files.at(i);
|
|
|
|
Dialect language = guessLanguageOfFile(fileName);
|
|
if (language == Dialect::NoLanguage) {
|
|
if (fileName.endsWith(QLatin1String(".qrc")))
|
|
modelManager->updateQrcFile(fileName);
|
|
continue;
|
|
}
|
|
if (language == Dialect::Qml
|
|
&& (mainLanguage == Dialect::QmlQtQuick1 || mainLanguage == Dialect::QmlQtQuick2))
|
|
language = mainLanguage;
|
|
if (language == Dialect::Qml && mainLanguage == Dialect::QmlQtQuick2Ui)
|
|
language = Dialect::QmlQtQuick2;
|
|
if (language == Dialect::QmlTypeInfo || language == Dialect::QmlProject)
|
|
continue;
|
|
QString contents;
|
|
int documentRevision = 0;
|
|
|
|
if (workingCopy.contains(fileName)) {
|
|
QPair<QString, int> entry = workingCopy.get(fileName);
|
|
contents = entry.first;
|
|
documentRevision = entry.second;
|
|
} else {
|
|
QFile inFile(fileName);
|
|
|
|
if (inFile.open(QIODevice::ReadOnly)) {
|
|
QTextStream ins(&inFile);
|
|
contents = ins.readAll();
|
|
inFile.close();
|
|
}
|
|
}
|
|
|
|
Document::MutablePtr doc = Document::create(fileName, language);
|
|
doc->setEditorRevision(documentRevision);
|
|
doc->setSource(contents);
|
|
doc->parse();
|
|
|
|
// update snapshot. requires synchronization, but significantly reduces amount of file
|
|
// system queries for library imports because queries are cached in libraryInfo
|
|
const Snapshot snapshot = modelManager->snapshot();
|
|
|
|
// get list of referenced files not yet in snapshot or in directories already scanned
|
|
QStringList importedFiles;
|
|
findNewImplicitImports(doc, snapshot, &importedFiles, &scannedPaths);
|
|
findNewFileImports(doc, snapshot, &importedFiles, &scannedPaths);
|
|
findNewLibraryImports(doc, snapshot, modelManager, &importedFiles, &scannedPaths, &newLibraries);
|
|
|
|
// add new files to parse list
|
|
foreach (const QString &file, importedFiles) {
|
|
if (! files.contains(file))
|
|
files.append(file);
|
|
}
|
|
|
|
modelManager->updateDocument(doc);
|
|
if (emitDocChangedOnDisk)
|
|
modelManager->emitDocumentChangedOnDisk(doc);
|
|
}
|
|
}
|
|
|
|
class FutureReporter
|
|
{
|
|
public:
|
|
FutureReporter(QFutureInterface<void> &future, int multiplier = 100, int base = 0)
|
|
:future(future), multiplier(multiplier), base(base)
|
|
{ }
|
|
bool operator()(qreal val)
|
|
{
|
|
if (future.isCanceled())
|
|
return false;
|
|
future.setProgressValue(int(base + multiplier * val));
|
|
return true;
|
|
}
|
|
private:
|
|
QFutureInterface<void> &future;
|
|
int multiplier;
|
|
int base;
|
|
};
|
|
|
|
void ModelManagerInterface::parse(QFutureInterface<void> &future,
|
|
WorkingCopy workingCopy,
|
|
QStringList files,
|
|
ModelManagerInterface *modelManager,
|
|
Dialect mainLanguage,
|
|
bool emitDocChangedOnDisk)
|
|
{
|
|
FutureReporter reporter(future);
|
|
future.setProgressRange(0, 100);
|
|
|
|
// paths we have scanned for files and added to the files list
|
|
QSet<QString> scannedPaths;
|
|
// libraries we've found while scanning imports
|
|
QSet<QString> newLibraries;
|
|
parseLoop(scannedPaths, newLibraries, workingCopy, files, modelManager, mainLanguage,
|
|
emitDocChangedOnDisk, reporter);
|
|
future.setProgressValue(100);
|
|
}
|
|
|
|
struct ScanItem {
|
|
QString path;
|
|
int depth;
|
|
Dialect language;
|
|
ScanItem(QString path = QString(), int depth = 0, Dialect language = Dialect::AnyLanguage)
|
|
: path(path), depth(depth), language(language)
|
|
{ }
|
|
};
|
|
|
|
void ModelManagerInterface::importScan(QFutureInterface<void> &future,
|
|
ModelManagerInterface::WorkingCopy workingCopy,
|
|
PathsAndLanguages paths, ModelManagerInterface *modelManager,
|
|
bool emitDocChangedOnDisk, bool libOnly)
|
|
{
|
|
// paths we have scanned for files and added to the files list
|
|
QSet<QString> scannedPaths;
|
|
{
|
|
QMutexLocker l(&modelManager->m_mutex);
|
|
scannedPaths = modelManager->m_scannedPaths;
|
|
}
|
|
// libraries we've found while scanning imports
|
|
QSet<QString> newLibraries;
|
|
|
|
QVector<ScanItem> pathsToScan;
|
|
pathsToScan.reserve(paths.size());
|
|
{
|
|
QMutexLocker l(&modelManager->m_mutex);
|
|
for (int i = 0; i < paths.size(); ++i) {
|
|
PathAndLanguage pAndL = paths.at(i);
|
|
QString cPath = QDir::cleanPath(pAndL.path().toString());
|
|
if (modelManager->m_scannedPaths.contains(cPath))
|
|
continue;
|
|
pathsToScan.append(ScanItem(cPath, 0, pAndL.language()));
|
|
modelManager->m_scannedPaths.insert(cPath);
|
|
}
|
|
}
|
|
const int maxScanDepth = 5;
|
|
int progressRange = pathsToScan.size() * (1 << (2 + maxScanDepth));
|
|
int totalWork(progressRange), workDone(0);
|
|
future.setProgressRange(0, progressRange); // update max length while iterating?
|
|
const Snapshot snapshot = modelManager->snapshot();
|
|
bool isCanceled = future.isCanceled();
|
|
while (!pathsToScan.isEmpty() && !isCanceled) {
|
|
ScanItem toScan = pathsToScan.last();
|
|
pathsToScan.pop_back();
|
|
int pathBudget = (1 << (maxScanDepth + 2 - toScan.depth));
|
|
if (!scannedPaths.contains(toScan.path)) {
|
|
QStringList importedFiles;
|
|
if (!findNewQmlLibraryInPath(toScan.path, snapshot, modelManager, &importedFiles,
|
|
&scannedPaths, &newLibraries, true)
|
|
&& !libOnly && snapshot.documentsInDirectory(toScan.path).isEmpty())
|
|
importedFiles += filesInDirectoryForLanguages(toScan.path,
|
|
toScan.language.companionLanguages());
|
|
workDone += 1;
|
|
future.setProgressValue(progressRange * workDone / totalWork);
|
|
if (!importedFiles.isEmpty()) {
|
|
FutureReporter reporter(future, progressRange * pathBudget / (4 * totalWork),
|
|
progressRange * workDone / totalWork);
|
|
parseLoop(scannedPaths, newLibraries, workingCopy, importedFiles, modelManager,
|
|
toScan.language, emitDocChangedOnDisk, reporter); // run in parallel??
|
|
importedFiles.clear();
|
|
}
|
|
workDone += pathBudget / 4 - 1;
|
|
future.setProgressValue(progressRange * workDone / totalWork);
|
|
} else {
|
|
workDone += pathBudget / 4;
|
|
}
|
|
// always descend tree, as we might have just scanned with a smaller depth
|
|
if (toScan.depth < maxScanDepth) {
|
|
QDir dir(toScan.path);
|
|
QStringList subDirs(dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot));
|
|
workDone += 1;
|
|
totalWork += pathBudget / 2 * subDirs.size() - pathBudget * 3 / 4 + 1;
|
|
foreach (const QString path, subDirs)
|
|
pathsToScan.append(ScanItem(dir.absoluteFilePath(path), toScan.depth + 1, toScan.language));
|
|
} else {
|
|
workDone += pathBudget * 3 / 4;
|
|
}
|
|
future.setProgressValue(progressRange * workDone / totalWork);
|
|
isCanceled = future.isCanceled();
|
|
}
|
|
future.setProgressValue(progressRange);
|
|
if (isCanceled) {
|
|
// assume no work has been done
|
|
QMutexLocker l(&modelManager->m_mutex);
|
|
for (int i = 0; i < paths.size(); ++i)
|
|
modelManager->m_scannedPaths.remove(paths.at(i).path().toString());
|
|
}
|
|
}
|
|
|
|
PathsAndLanguages ModelManagerInterface::importPaths() const
|
|
{
|
|
QMutexLocker l(&m_mutex);
|
|
return m_allImportPaths;
|
|
}
|
|
|
|
QmlLanguageBundles ModelManagerInterface::activeBundles() const
|
|
{
|
|
QMutexLocker l(&m_mutex);
|
|
return m_activeBundles;
|
|
}
|
|
|
|
QmlLanguageBundles ModelManagerInterface::extendedBundles() const
|
|
{
|
|
QMutexLocker l(&m_mutex);
|
|
return m_extendedBundles;
|
|
}
|
|
|
|
void ModelManagerInterface::maybeScan(const PathsAndLanguages &importPaths)
|
|
{
|
|
if (!m_indexerEnabled)
|
|
return;
|
|
PathsAndLanguages pathToScan;
|
|
{
|
|
QMutexLocker l(&m_mutex);
|
|
foreach (const PathAndLanguage &importPath, importPaths)
|
|
if (!m_scannedPaths.contains(importPath.path().toString()))
|
|
pathToScan.maybeInsert(importPath);
|
|
}
|
|
|
|
if (pathToScan.length() > 1) {
|
|
QFuture<void> result = Utils::runAsync(&ModelManagerInterface::importScan,
|
|
workingCopyInternal(), pathToScan,
|
|
this, true, true);
|
|
cleanupFutures();
|
|
m_futures.append(result);
|
|
|
|
addTaskInternal(result, tr("Scanning QML Imports"), Constants::TASK_IMPORT_SCAN);
|
|
}
|
|
}
|
|
|
|
void ModelManagerInterface::updateImportPaths()
|
|
{
|
|
if (!m_indexerEnabled)
|
|
return;
|
|
PathsAndLanguages allImportPaths;
|
|
QmlLanguageBundles activeBundles;
|
|
QmlLanguageBundles extendedBundles;
|
|
QMapIterator<ProjectExplorer::Project *, ProjectInfo> pInfoIter(m_projects);
|
|
QHashIterator<Dialect, QmlJS::ViewerContext> vCtxsIter = m_defaultVContexts;
|
|
while (pInfoIter.hasNext()) {
|
|
pInfoIter.next();
|
|
const PathsAndLanguages &iPaths = pInfoIter.value().importPaths;
|
|
for (int i = 0; i < iPaths.size(); ++i) {
|
|
PathAndLanguage pAndL = iPaths.at(i);
|
|
const QString canonicalPath = pAndL.path().toFileInfo().canonicalFilePath();
|
|
if (!canonicalPath.isEmpty())
|
|
allImportPaths.maybeInsert(Utils::FileName::fromString(canonicalPath),
|
|
pAndL.language());
|
|
}
|
|
}
|
|
while (vCtxsIter.hasNext()) {
|
|
vCtxsIter.next();
|
|
foreach (const QString &path, vCtxsIter.value().paths)
|
|
allImportPaths.maybeInsert(Utils::FileName::fromString(path), vCtxsIter.value().language);
|
|
}
|
|
pInfoIter.toFront();
|
|
while (pInfoIter.hasNext()) {
|
|
pInfoIter.next();
|
|
activeBundles.mergeLanguageBundles(pInfoIter.value().activeBundle);
|
|
foreach (Dialect l, pInfoIter.value().activeBundle.languages()) {
|
|
foreach (const QString &path, pInfoIter.value().activeBundle.bundleForLanguage(l)
|
|
.searchPaths().stringList()) {
|
|
const QString canonicalPath = QFileInfo(path).canonicalFilePath();
|
|
if (!canonicalPath.isEmpty())
|
|
allImportPaths.maybeInsert(Utils::FileName::fromString(canonicalPath), l);
|
|
}
|
|
}
|
|
}
|
|
pInfoIter.toFront();
|
|
while (pInfoIter.hasNext()) {
|
|
pInfoIter.next();
|
|
QString pathAtt = pInfoIter.value().qtQmlPath;
|
|
if (!pathAtt.isEmpty())
|
|
allImportPaths.maybeInsert(Utils::FileName::fromString(pathAtt), Dialect::QmlQtQuick2);
|
|
}
|
|
{
|
|
QString pathAtt = defaultProjectInfo().qtQmlPath;
|
|
if (!pathAtt.isEmpty())
|
|
allImportPaths.maybeInsert(Utils::FileName::fromString(pathAtt), Dialect::QmlQtQuick2);
|
|
}
|
|
pInfoIter.toFront();
|
|
while (pInfoIter.hasNext()) {
|
|
pInfoIter.next();
|
|
QString pathAtt = pInfoIter.value().qtImportsPath;
|
|
if (!pathAtt.isEmpty())
|
|
allImportPaths.maybeInsert(Utils::FileName::fromString(pathAtt), Dialect::QmlQtQuick1);
|
|
}
|
|
{
|
|
QString pathAtt = defaultProjectInfo().qtImportsPath;
|
|
if (!pathAtt.isEmpty())
|
|
allImportPaths.maybeInsert(Utils::FileName::fromString(pathAtt), Dialect::QmlQtQuick1);
|
|
}
|
|
foreach (const QString &path, m_defaultImportPaths)
|
|
allImportPaths.maybeInsert(Utils::FileName::fromString(path), Dialect::Qml);
|
|
allImportPaths.compact();
|
|
|
|
{
|
|
QMutexLocker l(&m_mutex);
|
|
m_allImportPaths = allImportPaths;
|
|
m_activeBundles = activeBundles;
|
|
m_extendedBundles = extendedBundles;
|
|
}
|
|
|
|
|
|
// check if any file in the snapshot imports something new in the new paths
|
|
Snapshot snapshot = m_validSnapshot;
|
|
QStringList importedFiles;
|
|
QSet<QString> scannedPaths;
|
|
QSet<QString> newLibraries;
|
|
foreach (const Document::Ptr &doc, snapshot)
|
|
findNewLibraryImports(doc, snapshot, this, &importedFiles, &scannedPaths, &newLibraries);
|
|
|
|
updateSourceFiles(importedFiles, true);
|
|
|
|
if (!m_shouldScanImports)
|
|
return;
|
|
maybeScan(allImportPaths);
|
|
}
|
|
|
|
void ModelManagerInterface::loadPluginTypes(const QString &libraryPath, const QString &importPath,
|
|
const QString &importUri, const QString &importVersion)
|
|
{
|
|
m_pluginDumper->loadPluginTypes(libraryPath, importPath, importUri, importVersion);
|
|
}
|
|
|
|
// is called *inside a c++ parsing thread*, to allow hanging on to source and ast
|
|
void ModelManagerInterface::maybeQueueCppQmlTypeUpdate(const CPlusPlus::Document::Ptr &doc)
|
|
{
|
|
// avoid scanning documents without source code available
|
|
doc->keepSourceAndAST();
|
|
if (doc->utf8Source().isEmpty()) {
|
|
doc->releaseSourceAndAST();
|
|
return;
|
|
}
|
|
|
|
// keep source and AST alive if we want to scan for register calls
|
|
const bool scan = FindExportedCppTypes::maybeExportsTypes(doc);
|
|
if (!scan)
|
|
doc->releaseSourceAndAST();
|
|
|
|
// delegate actual queuing to the gui thread
|
|
QMetaObject::invokeMethod(this, "queueCppQmlTypeUpdate",
|
|
Q_ARG(CPlusPlus::Document::Ptr, doc), Q_ARG(bool, scan));
|
|
}
|
|
|
|
void ModelManagerInterface::queueCppQmlTypeUpdate(const CPlusPlus::Document::Ptr &doc, bool scan)
|
|
{
|
|
QPair<CPlusPlus::Document::Ptr, bool> prev = m_queuedCppDocuments.value(doc->fileName());
|
|
if (prev.first && prev.second)
|
|
prev.first->releaseSourceAndAST();
|
|
m_queuedCppDocuments.insert(doc->fileName(), qMakePair(doc, scan));
|
|
m_updateCppQmlTypesTimer->start();
|
|
}
|
|
|
|
void ModelManagerInterface::startCppQmlTypeUpdate()
|
|
{
|
|
// if a future is still running, delay
|
|
if (m_cppQmlTypesUpdater.isRunning()) {
|
|
m_updateCppQmlTypesTimer->start();
|
|
return;
|
|
}
|
|
|
|
CPlusPlus::CppModelManagerBase *cppModelManager =
|
|
CPlusPlus::CppModelManagerBase::instance();
|
|
if (!cppModelManager)
|
|
return;
|
|
|
|
m_cppQmlTypesUpdater = Utils::runAsync(&ModelManagerInterface::updateCppQmlTypes,
|
|
this, cppModelManager->snapshot(), m_queuedCppDocuments);
|
|
m_queuedCppDocuments.clear();
|
|
}
|
|
|
|
QMutex *ModelManagerInterface::mutex() const
|
|
{
|
|
return &m_mutex;
|
|
}
|
|
|
|
void ModelManagerInterface::asyncReset()
|
|
{
|
|
m_asyncResetTimer->start();
|
|
}
|
|
|
|
bool rescanExports(const QString &fileName, FindExportedCppTypes &finder,
|
|
ModelManagerInterface::CppDataHash &newData)
|
|
{
|
|
bool hasNewInfo = false;
|
|
|
|
QList<LanguageUtils::FakeMetaObject::ConstPtr> exported = finder.exportedTypes();
|
|
QHash<QString, QString> contextProperties = finder.contextProperties();
|
|
if (exported.isEmpty() && contextProperties.isEmpty()) {
|
|
hasNewInfo = hasNewInfo || newData.remove(fileName) > 0;
|
|
} else {
|
|
ModelManagerInterface::CppData &data = newData[fileName];
|
|
if (!hasNewInfo && (data.exportedTypes.size() != exported.size()
|
|
|| data.contextProperties != contextProperties))
|
|
hasNewInfo = true;
|
|
if (!hasNewInfo) {
|
|
QHash<QString, QByteArray> newFingerprints;
|
|
foreach (LanguageUtils::FakeMetaObject::ConstPtr newType, exported)
|
|
newFingerprints[newType->className()]=newType->fingerprint();
|
|
foreach (LanguageUtils::FakeMetaObject::ConstPtr oldType, data.exportedTypes) {
|
|
if (newFingerprints.value(oldType->className()) != oldType->fingerprint()) {
|
|
hasNewInfo = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
data.exportedTypes = exported;
|
|
data.contextProperties = contextProperties;
|
|
}
|
|
return hasNewInfo;
|
|
}
|
|
|
|
void ModelManagerInterface::updateCppQmlTypes(QFutureInterface<void> &interface,
|
|
ModelManagerInterface *qmlModelManager,
|
|
CPlusPlus::Snapshot snapshot,
|
|
QHash<QString, QPair<CPlusPlus::Document::Ptr, bool> > documents)
|
|
{
|
|
interface.setProgressRange(0, documents.size());
|
|
interface.setProgressValue(0);
|
|
|
|
CppDataHash newData;
|
|
QHash<QString, QList<CPlusPlus::Document::Ptr> > newDeclarations;
|
|
{
|
|
QMutexLocker locker(&qmlModelManager->m_cppDataMutex);
|
|
newData = qmlModelManager->m_cppDataHash;
|
|
newDeclarations = qmlModelManager->m_cppDeclarationFiles;
|
|
}
|
|
|
|
FindExportedCppTypes finder(snapshot);
|
|
|
|
bool hasNewInfo = false;
|
|
typedef QPair<CPlusPlus::Document::Ptr, bool> DocScanPair;
|
|
foreach (const DocScanPair &pair, documents) {
|
|
if (interface.isCanceled())
|
|
return;
|
|
interface.setProgressValue(interface.progressValue() + 1);
|
|
|
|
CPlusPlus::Document::Ptr doc = pair.first;
|
|
const bool scan = pair.second;
|
|
const QString fileName = doc->fileName();
|
|
if (!scan) {
|
|
hasNewInfo = newData.remove(fileName) > 0 || hasNewInfo;
|
|
foreach (const CPlusPlus::Document::Ptr &savedDoc, newDeclarations.value(fileName)) {
|
|
finder(savedDoc);
|
|
hasNewInfo = rescanExports(savedDoc->fileName(), finder, newData) || hasNewInfo;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
for (auto it = newDeclarations.begin(), end = newDeclarations.end(); it != end;) {
|
|
for (auto docIt = it->begin(), endDocIt = it->end(); docIt != endDocIt;) {
|
|
CPlusPlus::Document::Ptr &savedDoc = *docIt;
|
|
if (savedDoc->fileName() == fileName) {
|
|
savedDoc->releaseSourceAndAST();
|
|
it->erase(docIt);
|
|
break;
|
|
} else {
|
|
++docIt;
|
|
}
|
|
}
|
|
if (it->isEmpty())
|
|
it = newDeclarations.erase(it);
|
|
else
|
|
++it;
|
|
}
|
|
|
|
foreach (const QString &declarationFile, finder(doc)) {
|
|
newDeclarations[declarationFile].append(doc);
|
|
doc->keepSourceAndAST(); // keep for later reparsing when dependent doc changes
|
|
}
|
|
|
|
hasNewInfo = rescanExports(fileName, finder, newData) || hasNewInfo;
|
|
doc->releaseSourceAndAST();
|
|
}
|
|
|
|
QMutexLocker locker(&qmlModelManager->m_cppDataMutex);
|
|
qmlModelManager->m_cppDataHash = newData;
|
|
qmlModelManager->m_cppDeclarationFiles = newDeclarations;
|
|
if (hasNewInfo)
|
|
// one could get away with re-linking the cpp types...
|
|
QMetaObject::invokeMethod(qmlModelManager, "asyncReset");
|
|
}
|
|
|
|
ModelManagerInterface::CppDataHash ModelManagerInterface::cppData() const
|
|
{
|
|
QMutexLocker locker(&m_cppDataMutex);
|
|
return m_cppDataHash;
|
|
}
|
|
|
|
LibraryInfo ModelManagerInterface::builtins(const Document::Ptr &doc) const
|
|
{
|
|
ProjectInfo info = projectInfoForPath(doc->fileName());
|
|
if (!info.isValid())
|
|
return LibraryInfo();
|
|
if (!info.qtQmlPath.isEmpty())
|
|
return m_validSnapshot.libraryInfo(info.qtQmlPath);
|
|
return m_validSnapshot.libraryInfo(info.qtImportsPath);
|
|
}
|
|
|
|
ViewerContext ModelManagerInterface::completeVContext(const ViewerContext &vCtx,
|
|
const Document::Ptr &doc) const
|
|
{
|
|
ViewerContext res = vCtx;
|
|
|
|
if (!doc.isNull()
|
|
&& ((vCtx.language == Dialect::AnyLanguage && doc->language() != Dialect::NoLanguage)
|
|
|| (vCtx.language == Dialect::Qml
|
|
&& (doc->language() == Dialect::QmlQtQuick1
|
|
|| doc->language() == Dialect::QmlQtQuick2
|
|
|| doc->language() == Dialect::QmlQtQuick2Ui))))
|
|
res.language = doc->language();
|
|
ProjectInfo info;
|
|
if (!doc.isNull())
|
|
info = projectInfoForPath(doc->fileName());
|
|
ViewerContext defaultVCtx = defaultVContext(res.language, Document::Ptr(0), false);
|
|
ProjectInfo defaultInfo = defaultProjectInfo();
|
|
if (info.qtImportsPath.isEmpty())
|
|
info.qtImportsPath = defaultInfo.qtImportsPath;
|
|
if (info.qtQmlPath.isEmpty())
|
|
info.qtQmlPath = defaultInfo.qtQmlPath;
|
|
switch (res.flags) {
|
|
case ViewerContext::Complete:
|
|
break;
|
|
case ViewerContext::AddAllPathsAndDefaultSelectors:
|
|
res.selectors.append(defaultVCtx.selectors);
|
|
// fallthrough
|
|
case ViewerContext::AddAllPaths:
|
|
{
|
|
foreach (const QString &path, defaultVCtx.paths)
|
|
res.maybeAddPath(path);
|
|
switch (res.language.dialect()) {
|
|
case Dialect::AnyLanguage:
|
|
case Dialect::Qml:
|
|
res.maybeAddPath(info.qtQmlPath);
|
|
// fallthrough
|
|
case Dialect::QmlQtQuick1:
|
|
res.maybeAddPath(info.qtImportsPath);
|
|
// fallthrough
|
|
case Dialect::QmlQtQuick2:
|
|
case Dialect::QmlQtQuick2Ui:
|
|
{
|
|
if (res.language == Dialect::QmlQtQuick2 || res.language == Dialect::QmlQtQuick2Ui)
|
|
res.maybeAddPath(info.qtQmlPath);
|
|
QList<ProjectInfo> allProjects;
|
|
{
|
|
QMutexLocker locker(&m_mutex);
|
|
allProjects = m_projects.values();
|
|
}
|
|
std::sort(allProjects.begin(), allProjects.end(), &pInfoLessThanImports);
|
|
QList<Dialect> languages = res.language.companionLanguages();
|
|
foreach (const ProjectInfo &pInfo, allProjects) {
|
|
for (int i = 0; i< pInfo.importPaths.size(); ++i) {
|
|
PathAndLanguage pAndL = pInfo.importPaths.at(i);
|
|
if (languages.contains(pAndL.language()) || pAndL.language().companionLanguages().contains(res.language))
|
|
res.maybeAddPath(pAndL.path().toString());
|
|
}
|
|
}
|
|
foreach (const QString &path, environmentImportPaths())
|
|
res.maybeAddPath(path);
|
|
break;
|
|
}
|
|
case Dialect::NoLanguage:
|
|
case Dialect::JavaScript:
|
|
case Dialect::QmlTypeInfo:
|
|
case Dialect::Json:
|
|
case Dialect::QmlQbs:
|
|
case Dialect::QmlProject:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case ViewerContext::AddDefaultPathsAndSelectors:
|
|
res.selectors.append(defaultVCtx.selectors);
|
|
// fallthrough
|
|
case ViewerContext::AddDefaultPaths:
|
|
foreach (const QString &path, defaultVCtx.paths)
|
|
res.maybeAddPath(path);
|
|
if (res.language == Dialect::AnyLanguage || res.language == Dialect::Qml
|
|
|| res.language == Dialect::QmlQtQuick2 || res.language == Dialect::QmlQtQuick2Ui)
|
|
res.maybeAddPath(info.qtImportsPath);
|
|
if (res.language == Dialect::AnyLanguage || res.language == Dialect::Qml
|
|
|| res.language == Dialect::QmlQtQuick1)
|
|
res.maybeAddPath(info.qtQmlPath);
|
|
if (res.language == Dialect::AnyLanguage || res.language == Dialect::Qml
|
|
|| res.language == Dialect::QmlQtQuick1 || res.language == Dialect::QmlQtQuick2
|
|
|| res.language == Dialect::QmlQtQuick2Ui) {
|
|
foreach (const QString &path, environmentImportPaths())
|
|
res.maybeAddPath(path);
|
|
}
|
|
break;
|
|
}
|
|
res.flags = ViewerContext::Complete;
|
|
return res;
|
|
}
|
|
|
|
ViewerContext ModelManagerInterface::defaultVContext(Dialect language,
|
|
const Document::Ptr &doc,
|
|
bool autoComplete) const
|
|
{
|
|
if (!doc.isNull()) {
|
|
if (language == Dialect::AnyLanguage && doc->language() != Dialect::NoLanguage)
|
|
language = doc->language();
|
|
else if (language == Dialect::Qml &&
|
|
(doc->language() == Dialect::QmlQtQuick1 || doc->language() == Dialect::QmlQtQuick2
|
|
|| doc->language() == Dialect::QmlQtQuick2Ui))
|
|
language = doc->language();
|
|
}
|
|
ViewerContext defaultCtx;
|
|
{
|
|
QMutexLocker locker(&m_mutex);
|
|
defaultCtx = m_defaultVContexts.value(language);
|
|
}
|
|
defaultCtx.language = language;
|
|
if (autoComplete)
|
|
return completeVContext(defaultCtx, doc);
|
|
else
|
|
return defaultCtx;
|
|
}
|
|
|
|
ModelManagerInterface::ProjectInfo ModelManagerInterface::defaultProjectInfo() const
|
|
{
|
|
QMutexLocker l(mutex());
|
|
return m_defaultProjectInfo;
|
|
}
|
|
|
|
ModelManagerInterface::ProjectInfo ModelManagerInterface::defaultProjectInfoForProject(
|
|
ProjectExplorer::Project *) const
|
|
{
|
|
return ModelManagerInterface::ProjectInfo();
|
|
}
|
|
|
|
void ModelManagerInterface::setDefaultVContext(const ViewerContext &vContext)
|
|
{
|
|
QMutexLocker locker(&m_mutex);
|
|
m_defaultVContexts[vContext.language] = vContext;
|
|
}
|
|
|
|
void ModelManagerInterface::joinAllThreads()
|
|
{
|
|
foreach (QFuture<void> future, m_futures)
|
|
future.waitForFinished();
|
|
m_futures.clear();
|
|
}
|
|
|
|
Document::Ptr ModelManagerInterface::ensuredGetDocumentForPath(const QString &filePath)
|
|
{
|
|
QmlJS::Document::Ptr document = newestSnapshot().document(filePath);
|
|
if (!document) {
|
|
document = QmlJS::Document::create(filePath, QmlJS::Dialect::Qml);
|
|
QMutexLocker lock(&m_mutex);
|
|
|
|
m_newestSnapshot.insert(document);
|
|
}
|
|
|
|
return document;
|
|
}
|
|
|
|
void ModelManagerInterface::resetCodeModel()
|
|
{
|
|
QStringList documents;
|
|
|
|
{
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
// find all documents currently in the code model
|
|
foreach (Document::Ptr doc, m_validSnapshot)
|
|
documents.append(doc->fileName());
|
|
|
|
// reset the snapshot
|
|
m_validSnapshot = Snapshot();
|
|
m_newestSnapshot = Snapshot();
|
|
}
|
|
|
|
// start a reparse thread
|
|
updateSourceFiles(documents, false);
|
|
}
|
|
|
|
} // namespace QmlJS
|