2012-10-02 09:12:39 +02:00
|
|
|
/****************************************************************************
|
2009-09-04 16:51:11 +02:00
|
|
|
**
|
2016-01-15 14:58:39 +01:00
|
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
2009-09-04 16:51:11 +02:00
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
** This file is part of Qt Creator.
|
2009-09-04 16:51:11 +02:00
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
** 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
|
2016-01-15 14:58:39 +01:00
|
|
|
** 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.
|
2009-09-04 16:51:11 +02:00
|
|
|
**
|
2016-01-15 14:58:39 +01:00
|
|
|
** 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.
|
2010-12-17 16:01:08 +01:00
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
****************************************************************************/
|
2009-09-04 16:51:11 +02:00
|
|
|
|
2014-01-23 14:28:31 +01:00
|
|
|
#include "qmljsbind.h"
|
|
|
|
#include "qmljsconstants.h"
|
|
|
|
#include "qmljsfindexportedcpptypes.h"
|
|
|
|
#include "qmljsinterpreter.h"
|
2010-06-09 15:56:03 +02:00
|
|
|
#include "qmljsmodelmanagerinterface.h"
|
2014-01-23 14:28:31 +01:00
|
|
|
#include "qmljsplugindumper.h"
|
|
|
|
#include "qmljstypedescriptionreader.h"
|
2014-07-22 19:06:44 +02:00
|
|
|
#include "qmljsdialect.h"
|
2015-03-04 16:46:23 +01:00
|
|
|
#include "qmljsviewercontext.h"
|
2017-09-28 16:24:13 +02:00
|
|
|
#include "qmljsutils.h"
|
2009-09-04 16:51:11 +02:00
|
|
|
|
2014-01-23 14:28:31 +01:00
|
|
|
#include <cplusplus/cppmodelmanagerbase.h>
|
2016-08-03 23:29:58 +03:00
|
|
|
#include <utils/algorithm.h>
|
2014-01-23 14:28:31 +01:00
|
|
|
#include <utils/hostosinfo.h>
|
2014-04-11 23:07:52 +02:00
|
|
|
#include <utils/runextensions.h>
|
2020-06-18 19:04:58 +02:00
|
|
|
#include <utils/stringutils.h>
|
2014-01-23 14:28:31 +01:00
|
|
|
|
2021-04-30 16:52:03 +02:00
|
|
|
#ifdef WITH_TESTS
|
|
|
|
#include <extensionsystem/pluginmanager.h>
|
|
|
|
#endif
|
|
|
|
|
2014-01-23 14:28:31 +01:00
|
|
|
#include <QDir>
|
2020-03-26 12:17:03 +01:00
|
|
|
#include <QDirIterator>
|
2014-01-23 14:28:31 +01:00
|
|
|
#include <QFile>
|
2014-01-22 18:38:45 +01:00
|
|
|
#include <QFileInfo>
|
2016-01-27 22:36:48 +02:00
|
|
|
#include <QMetaObject>
|
2014-01-23 14:28:31 +01:00
|
|
|
#include <QTextDocument>
|
|
|
|
#include <QTextStream>
|
|
|
|
#include <QTimer>
|
|
|
|
#include <QtAlgorithms>
|
2014-04-11 23:07:52 +02:00
|
|
|
#include <QLibraryInfo>
|
2014-01-23 14:28:31 +01:00
|
|
|
|
2019-02-07 17:22:39 +01:00
|
|
|
using namespace Utils;
|
|
|
|
|
2014-01-23 14:28:31 +01:00
|
|
|
namespace QmlJS {
|
2009-09-04 16:51:11 +02:00
|
|
|
|
2018-10-12 09:33:30 +03:00
|
|
|
QMLJS_EXPORT Q_LOGGING_CATEGORY(qmljsLog, "qtc.qmljs.common", QtWarningMsg)
|
2014-05-27 10:39:50 +02:00
|
|
|
|
2011-08-30 09:19:56 +02:00
|
|
|
/*!
|
|
|
|
\class QmlJS::ModelManagerInterface
|
2013-06-05 14:29:24 +02:00
|
|
|
\brief The ModelManagerInterface class acts as an interface to the
|
|
|
|
global state of the QmlJS code model.
|
2011-08-30 09:19:56 +02:00
|
|
|
\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().
|
|
|
|
*/
|
|
|
|
|
2019-01-16 14:28:59 +01:00
|
|
|
static ModelManagerInterface *g_instance = nullptr;
|
2021-06-21 13:51:55 +02:00
|
|
|
static QMutex g_instanceMutex;
|
2019-10-23 14:03:13 +02:00
|
|
|
static const char *qtQuickUISuffix = "ui.qml";
|
2014-10-13 16:46:30 +02:00
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
static void maybeAddPath(ViewerContext &context, const Utils::FilePath &path)
|
2019-10-23 09:14:58 +02:00
|
|
|
{
|
|
|
|
if (!path.isEmpty() && !context.paths.contains(path))
|
|
|
|
context.paths.append(path);
|
|
|
|
}
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
static QList<Utils::FilePath> environmentImportPaths()
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
2022-06-20 12:35:13 +02:00
|
|
|
QList<Utils::FilePath> paths;
|
2014-01-23 14:28:31 +01:00
|
|
|
|
2019-01-18 20:28:55 +01:00
|
|
|
const QStringList importPaths = QString::fromLocal8Bit(qgetenv("QML_IMPORT_PATH")).split(
|
2020-07-21 10:19:36 +02:00
|
|
|
Utils::HostOsInfo::pathListSeparator(), Qt::SkipEmptyParts);
|
2014-01-23 14:28:31 +01:00
|
|
|
|
2019-01-18 20:28:55 +01:00
|
|
|
for (const QString &path : importPaths) {
|
2022-06-20 12:35:13 +02:00
|
|
|
const Utils::FilePath canonicalPath = Utils::FilePath::fromString(path).canonicalPath();
|
2014-01-23 14:28:31 +01:00
|
|
|
if (!canonicalPath.isEmpty() && !paths.contains(canonicalPath))
|
|
|
|
paths.append(canonicalPath);
|
|
|
|
}
|
|
|
|
|
|
|
|
return paths;
|
|
|
|
}
|
|
|
|
|
2010-06-09 15:56:03 +02:00
|
|
|
ModelManagerInterface::ModelManagerInterface(QObject *parent)
|
2014-01-23 14:28:31 +01:00
|
|
|
: QObject(parent),
|
2019-01-18 20:28:55 +01:00
|
|
|
m_defaultImportPaths(environmentImportPaths()),
|
2014-01-23 14:28:31 +01:00
|
|
|
m_pluginDumper(new PluginDumper(this))
|
2010-06-09 15:56:03 +02:00
|
|
|
{
|
Refactor tst_joinAllThreads, to be used in ModelManager d'tor
The idea in this approach is that we only collect those futures,
which have resulted from runAsync. The assumption is that
all tasks associated with those futures may sooner or
later finish, without the need to call qApp->processEvents().
OTOH, we don't collect fake futures coming from Utils::onFinished,
as these requires the spinning event loop in order to deliver
the onFinished signal.
So, the new joinAllThreads() method waits for all collected
futures to finish. We also _do_ want canceled and not finished
futures to finish, since even when they are canceled,
they may still be running and using the internals
of possibly destructed ModelManager. This means, we are only
waiting for other threads to be finished, without reporting
their results to e.g. onFinished() handlers.
Some tests require that all onFinished handlers are also processed.
In order to achieve this, we create a loop inside
tst_joinAllThreads() method and we call joinAllThreads(), so
it will wait for all pending queue to finish, and then we call process
events, in order to let finished futures propagate their results
to their respective onFinished() handlers.
Some handlers may have stared another threads when being processed,
so we may expect that some new futures will appear.
So, after processing the events we check if any new events
appeared, and in this case we repeat the loop.
Otherwise, we finish synchronization.
Amends: 96c860159b862460e21be16a6e2839c0b591e016
Task-number: QTCREATORBUG-25350
Change-Id: I5e44150c55f6be00445a5695938482d948990c94
Reviewed-by: Fawzi Mohamed <fawzi.mohamed@qt.io>
2021-03-05 13:54:50 +01:00
|
|
|
m_futureSynchronizer.setCancelOnWait(false);
|
2019-01-18 20:28:55 +01:00
|
|
|
m_indexerDisabled = qEnvironmentVariableIsSet("QTC_NO_CODE_INDEXER");
|
2014-01-23 14:28:31 +01:00
|
|
|
|
|
|
|
m_updateCppQmlTypesTimer = new QTimer(this);
|
2019-10-23 14:03:13 +02:00
|
|
|
const int second = 1000;
|
|
|
|
m_updateCppQmlTypesTimer->setInterval(second);
|
2014-01-23 14:28:31 +01:00
|
|
|
m_updateCppQmlTypesTimer->setSingleShot(true);
|
2016-06-27 22:25:11 +03:00
|
|
|
connect(m_updateCppQmlTypesTimer, &QTimer::timeout,
|
|
|
|
this, &ModelManagerInterface::startCppQmlTypeUpdate);
|
2014-01-23 14:28:31 +01:00
|
|
|
|
|
|
|
m_asyncResetTimer = new QTimer(this);
|
2019-10-23 14:03:13 +02:00
|
|
|
const int fifteenSeconds = 15000;
|
|
|
|
m_asyncResetTimer->setInterval(fifteenSeconds);
|
2014-01-23 14:28:31 +01:00
|
|
|
m_asyncResetTimer->setSingleShot(true);
|
2016-06-27 22:25:11 +03:00
|
|
|
connect(m_asyncResetTimer, &QTimer::timeout, this, &ModelManagerInterface::resetCodeModel);
|
2014-01-23 14:28:31 +01:00
|
|
|
|
|
|
|
qRegisterMetaType<QmlJS::Document::Ptr>("QmlJS::Document::Ptr");
|
|
|
|
qRegisterMetaType<QmlJS::LibraryInfo>("QmlJS::LibraryInfo");
|
2014-07-22 19:06:44 +02:00
|
|
|
qRegisterMetaType<QmlJS::Dialect>("QmlJS::Dialect");
|
|
|
|
qRegisterMetaType<QmlJS::PathAndLanguage>("QmlJS::PathAndLanguage");
|
|
|
|
qRegisterMetaType<QmlJS::PathsAndLanguages>("QmlJS::PathsAndLanguages");
|
2014-01-23 14:28:31 +01:00
|
|
|
|
2021-10-12 17:32:48 +02:00
|
|
|
m_defaultProjectInfo.qtQmlPath =
|
|
|
|
FilePath::fromUserInput(QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath));
|
2020-09-29 09:55:41 +02:00
|
|
|
m_defaultProjectInfo.qtVersionString = QLibraryInfo::version().toString();
|
2014-06-30 11:56:28 +02:00
|
|
|
|
2014-01-23 14:28:31 +01:00
|
|
|
updateImportPaths();
|
|
|
|
|
2021-06-21 13:51:55 +02:00
|
|
|
QMutexLocker locker(&g_instanceMutex);
|
2010-07-08 14:38:47 +02:00
|
|
|
Q_ASSERT(! g_instance);
|
|
|
|
g_instance = this;
|
2010-01-14 16:22:43 +01:00
|
|
|
}
|
|
|
|
|
2010-06-09 15:56:03 +02:00
|
|
|
ModelManagerInterface::~ModelManagerInterface()
|
2009-09-04 16:51:11 +02:00
|
|
|
{
|
2021-06-21 16:40:19 +02:00
|
|
|
Q_ASSERT(g_instance == this);
|
2014-02-13 11:11:40 +01:00
|
|
|
m_cppQmlTypesUpdater.cancel();
|
|
|
|
m_cppQmlTypesUpdater.waitForFinished();
|
2021-06-21 13:51:55 +02:00
|
|
|
|
2021-06-21 16:40:19 +02:00
|
|
|
while (true) {
|
|
|
|
joinAllThreads(true);
|
|
|
|
// Keep these 2 mutexes in the same order as inside instanceForFuture()
|
|
|
|
QMutexLocker instanceLocker(&g_instanceMutex);
|
|
|
|
QMutexLocker futureLocker(&m_futuresMutex);
|
|
|
|
if (m_futureSynchronizer.isEmpty()) {
|
|
|
|
g_instance = nullptr;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2010-06-09 15:56:03 +02:00
|
|
|
}
|
2009-09-04 16:51:11 +02:00
|
|
|
|
2014-07-22 19:06:44 +02:00
|
|
|
static QHash<QString, Dialect> defaultLanguageMapping()
|
2014-01-22 18:38:45 +01:00
|
|
|
{
|
2016-06-20 13:42:26 +02:00
|
|
|
static QHash<QString, Dialect> res{
|
2021-06-24 21:48:41 +02:00
|
|
|
{QLatin1String("mjs"), Dialect::JavaScript},
|
2016-06-20 13:42:26 +02:00
|
|
|
{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}
|
|
|
|
};
|
2014-01-22 18:38:45 +01:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
Dialect ModelManagerInterface::guessLanguageOfFile(const Utils::FilePath &fileName)
|
2014-01-22 18:38:45 +01:00
|
|
|
{
|
2014-07-22 19:06:44 +02:00
|
|
|
QHash<QString, Dialect> lMapping;
|
2014-01-22 18:38:45 +01:00
|
|
|
if (instance())
|
|
|
|
lMapping = instance()->languageForSuffix();
|
|
|
|
else
|
|
|
|
lMapping = defaultLanguageMapping();
|
2022-06-20 12:35:13 +02:00
|
|
|
QString fileSuffix = fileName.suffix();
|
2014-10-13 16:46:30 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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"))
|
2022-06-20 12:35:13 +02:00
|
|
|
fileSuffix = fileName.completeSuffix();
|
2014-10-13 16:46:30 +02:00
|
|
|
|
2014-07-22 19:06:44 +02:00
|
|
|
return lMapping.value(fileSuffix, Dialect::NoLanguage);
|
2014-01-22 18:38:45 +01:00
|
|
|
}
|
|
|
|
|
2019-10-23 14:03:13 +02:00
|
|
|
QStringList ModelManagerInterface::globPatternsForLanguages(const QList<Dialect> &languages)
|
2014-01-22 18:38:45 +01:00
|
|
|
{
|
|
|
|
QStringList patterns;
|
2019-07-24 13:43:54 +02:00
|
|
|
const QHash<QString, Dialect> lMapping =
|
|
|
|
instance() ? instance()->languageForSuffix() : defaultLanguageMapping();
|
|
|
|
for (auto i = lMapping.cbegin(), end = lMapping.cend(); i != end; ++i) {
|
2014-01-22 18:38:45 +01:00
|
|
|
if (languages.contains(i.value()))
|
|
|
|
patterns << QLatin1String("*.") + i.key();
|
|
|
|
}
|
|
|
|
return patterns;
|
|
|
|
}
|
|
|
|
|
2010-07-08 14:38:47 +02:00
|
|
|
ModelManagerInterface *ModelManagerInterface::instance()
|
|
|
|
{
|
|
|
|
return g_instance;
|
|
|
|
}
|
2012-12-06 17:20:58 +01:00
|
|
|
|
2021-06-21 13:51:55 +02:00
|
|
|
// If the returned instance is not null, it's guaranteed that it will be valid at least as long
|
|
|
|
// as the passed QFuture object isn't finished.
|
|
|
|
ModelManagerInterface *ModelManagerInterface::instanceForFuture(const QFuture<void> &future)
|
|
|
|
{
|
|
|
|
QMutexLocker locker(&g_instanceMutex);
|
|
|
|
if (g_instance)
|
|
|
|
g_instance->addFuture(future);
|
|
|
|
return g_instance;
|
|
|
|
}
|
2014-01-23 14:28:31 +01:00
|
|
|
void ModelManagerInterface::writeWarning(const QString &msg)
|
|
|
|
{
|
|
|
|
if (ModelManagerInterface *i = instance())
|
|
|
|
i->writeMessageInternal(msg);
|
|
|
|
else
|
2014-05-27 10:39:50 +02:00
|
|
|
qCWarning(qmljsLog) << msg;
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ModelManagerInterface::WorkingCopy ModelManagerInterface::workingCopy()
|
|
|
|
{
|
|
|
|
if (ModelManagerInterface *i = instance())
|
|
|
|
return i->workingCopyInternal();
|
|
|
|
return WorkingCopy();
|
|
|
|
}
|
|
|
|
|
2014-04-29 16:39:16 +02:00
|
|
|
void ModelManagerInterface::activateScan()
|
|
|
|
{
|
|
|
|
if (!m_shouldScanImports) {
|
|
|
|
m_shouldScanImports = true;
|
|
|
|
updateImportPaths();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-22 19:06:44 +02:00
|
|
|
QHash<QString, Dialect> ModelManagerInterface::languageForSuffix() const
|
2014-01-22 18:38:45 +01:00
|
|
|
{
|
|
|
|
return defaultLanguageMapping();
|
|
|
|
}
|
|
|
|
|
2014-01-23 14:28:31 +01:00
|
|
|
void ModelManagerInterface::writeMessageInternal(const QString &msg) const
|
|
|
|
{
|
2014-07-22 17:05:28 +02:00
|
|
|
qCDebug(qmljsLog) << msg;
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ModelManagerInterface::WorkingCopy ModelManagerInterface::workingCopyInternal() const
|
|
|
|
{
|
|
|
|
ModelManagerInterface::WorkingCopy res;
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2019-10-23 14:03:13 +02:00
|
|
|
void ModelManagerInterface::addTaskInternal(const QFuture<void> &result, const QString &msg,
|
2014-01-23 14:28:31 +01:00
|
|
|
const char *taskId) const
|
|
|
|
{
|
2019-07-23 10:58:00 +02:00
|
|
|
Q_UNUSED(result)
|
2014-05-27 10:39:50 +02:00
|
|
|
qCDebug(qmljsLog) << "started " << taskId << " " << msg;
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void ModelManagerInterface::loadQmlTypeDescriptionsInternal(const QString &resourcePath)
|
|
|
|
{
|
|
|
|
const QDir typeFileDir(resourcePath + QLatin1String("/qml-type-descriptions"));
|
2017-02-07 16:59:21 +01:00
|
|
|
const QStringList qmlTypesExtensions = QStringList("*.qmltypes");
|
2014-01-23 14:28:31 +01:00
|
|
|
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
|
2020-06-18 19:04:58 +02:00
|
|
|
const CppQmlTypesLoader::BuiltinObjects objs =
|
|
|
|
CppQmlTypesLoader::loadQmlTypes(qmlTypesFiles, &errors, &warnings);
|
|
|
|
for (auto it = objs.cbegin(); it != objs.cend(); ++it)
|
|
|
|
CppQmlTypesLoader::defaultLibraryObjects.insert(it.key(), it.value());
|
2014-01-23 14:28:31 +01:00
|
|
|
|
2019-10-23 14:03:13 +02:00
|
|
|
for (const QString &error : qAsConst(errors))
|
2014-01-23 14:28:31 +01:00
|
|
|
writeMessageInternal(error);
|
2019-10-23 14:03:13 +02:00
|
|
|
for (const QString &warning : qAsConst(warnings))
|
2014-01-23 14:28:31 +01:00
|
|
|
writeMessageInternal(warning);
|
|
|
|
}
|
|
|
|
|
2014-06-30 11:56:28 +02:00
|
|
|
void ModelManagerInterface::setDefaultProject(const ModelManagerInterface::ProjectInfo &pInfo,
|
|
|
|
ProjectExplorer::Project *p)
|
|
|
|
{
|
2021-02-18 20:09:26 +01:00
|
|
|
QMutexLocker locker(&m_mutex);
|
2014-06-30 11:56:28 +02:00
|
|
|
m_defaultProject = p;
|
|
|
|
m_defaultProjectInfo = pInfo;
|
|
|
|
}
|
2014-01-23 14:28:31 +01:00
|
|
|
|
|
|
|
Snapshot ModelManagerInterface::snapshot() const
|
|
|
|
{
|
|
|
|
QMutexLocker locker(&m_mutex);
|
2014-02-13 11:11:40 +01:00
|
|
|
return m_validSnapshot;
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Snapshot ModelManagerInterface::newestSnapshot() const
|
|
|
|
{
|
|
|
|
QMutexLocker locker(&m_mutex);
|
2014-02-13 11:11:40 +01:00
|
|
|
return m_newestSnapshot;
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
void ModelManagerInterface::updateSourceFiles(const QList<Utils::FilePath> &files,
|
2019-10-23 14:03:13 +02:00
|
|
|
bool emitDocumentOnDiskChanged)
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
2019-01-18 20:28:55 +01:00
|
|
|
if (m_indexerDisabled)
|
2014-09-08 17:59:25 +02:00
|
|
|
return;
|
2014-01-23 14:28:31 +01:00
|
|
|
refreshSourceFiles(files, emitDocumentOnDiskChanged);
|
|
|
|
}
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
QFuture<void> ModelManagerInterface::refreshSourceFiles(const QList<Utils::FilePath> &sourceFiles,
|
2019-10-23 14:03:13 +02:00
|
|
|
bool emitDocumentOnDiskChanged)
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
|
|
|
if (sourceFiles.isEmpty())
|
|
|
|
return QFuture<void>();
|
|
|
|
|
2016-02-08 16:26:19 +01:00
|
|
|
QFuture<void> result = Utils::runAsync(&ModelManagerInterface::parse,
|
|
|
|
workingCopyInternal(), sourceFiles,
|
|
|
|
this, Dialect(Dialect::Qml),
|
|
|
|
emitDocumentOnDiskChanged);
|
2020-05-10 16:00:31 +02:00
|
|
|
addFuture(result);
|
2014-01-23 14:28:31 +01:00
|
|
|
|
|
|
|
if (sourceFiles.count() > 1)
|
2014-04-17 15:14:14 +02:00
|
|
|
addTaskInternal(result, tr("Parsing QML Files"), Constants::TASK_INDEX);
|
2014-01-23 14:28:31 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
void ModelManagerInterface::fileChangedOnDisk(const Utils::FilePath &path)
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
Refactor tst_joinAllThreads, to be used in ModelManager d'tor
The idea in this approach is that we only collect those futures,
which have resulted from runAsync. The assumption is that
all tasks associated with those futures may sooner or
later finish, without the need to call qApp->processEvents().
OTOH, we don't collect fake futures coming from Utils::onFinished,
as these requires the spinning event loop in order to deliver
the onFinished signal.
So, the new joinAllThreads() method waits for all collected
futures to finish. We also _do_ want canceled and not finished
futures to finish, since even when they are canceled,
they may still be running and using the internals
of possibly destructed ModelManager. This means, we are only
waiting for other threads to be finished, without reporting
their results to e.g. onFinished() handlers.
Some tests require that all onFinished handlers are also processed.
In order to achieve this, we create a loop inside
tst_joinAllThreads() method and we call joinAllThreads(), so
it will wait for all pending queue to finish, and then we call process
events, in order to let finished futures propagate their results
to their respective onFinished() handlers.
Some handlers may have stared another threads when being processed,
so we may expect that some new futures will appear.
So, after processing the events we check if any new events
appeared, and in this case we repeat the loop.
Otherwise, we finish synchronization.
Amends: 96c860159b862460e21be16a6e2839c0b591e016
Task-number: QTCREATORBUG-25350
Change-Id: I5e44150c55f6be00445a5695938482d948990c94
Reviewed-by: Fawzi Mohamed <fawzi.mohamed@qt.io>
2021-03-05 13:54:50 +01:00
|
|
|
addFuture(Utils::runAsync(&ModelManagerInterface::parse,
|
2022-06-20 12:35:13 +02:00
|
|
|
workingCopyInternal(),
|
|
|
|
FilePaths({path}),
|
|
|
|
this,
|
|
|
|
Dialect(Dialect::AnyLanguage),
|
|
|
|
true));
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
void ModelManagerInterface::removeFiles(const QList<Utils::FilePath> &files)
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
|
|
|
emit aboutToRemoveFiles(files);
|
|
|
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
for (const Utils::FilePath &file : files) {
|
2014-02-13 11:11:40 +01:00
|
|
|
m_validSnapshot.remove(file);
|
|
|
|
m_newestSnapshot.remove(file);
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace {
|
2019-10-23 14:03:13 +02:00
|
|
|
bool pInfoLessThanActive(const ModelManagerInterface::ProjectInfo &p1,
|
|
|
|
const ModelManagerInterface::ProjectInfo &p2)
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
2022-06-20 12:35:13 +02:00
|
|
|
QList<Utils::FilePath> s1 = p1.activeResourceFiles;
|
|
|
|
QList<Utils::FilePath> s2 = p2.activeResourceFiles;
|
2014-01-23 14:28:31 +01:00
|
|
|
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;
|
2019-10-23 14:03:13 +02:00
|
|
|
if (s1.at(i) > s2.at(i))
|
2014-01-23 14:28:31 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-10-23 14:03:13 +02:00
|
|
|
bool pInfoLessThanAll(const ModelManagerInterface::ProjectInfo &p1,
|
|
|
|
const ModelManagerInterface::ProjectInfo &p2)
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
2022-06-20 12:35:13 +02:00
|
|
|
QList<Utils::FilePath> s1 = p1.allResourceFiles;
|
|
|
|
QList<Utils::FilePath> s2 = p2.allResourceFiles;
|
2014-01-23 14:28:31 +01:00
|
|
|
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;
|
2019-10-23 14:03:13 +02:00
|
|
|
if (s1.at(i) > s2.at(i))
|
2014-01-23 14:28:31 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2014-04-11 23:07:52 +02:00
|
|
|
|
2019-10-23 14:03:13 +02:00
|
|
|
bool pInfoLessThanImports(const ModelManagerInterface::ProjectInfo &p1,
|
|
|
|
const ModelManagerInterface::ProjectInfo &p2)
|
2014-04-11 23:07:52 +02:00
|
|
|
{
|
|
|
|
if (p1.qtQmlPath < p2.qtQmlPath)
|
|
|
|
return true;
|
|
|
|
if (p1.qtQmlPath > p2.qtQmlPath)
|
|
|
|
return false;
|
2014-07-22 19:06:44 +02:00
|
|
|
const PathsAndLanguages &s1 = p1.importPaths;
|
|
|
|
const PathsAndLanguages &s2 = p2.importPaths;
|
2014-04-11 23:07:52 +02:00
|
|
|
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;
|
2019-10-23 14:03:13 +02:00
|
|
|
if (s2.at(i) < s1.at(i))
|
2014-04-11 23:07:52 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
static QList<Utils::FilePath> generatedQrc(QList<Utils::FilePath> applicationDirectories)
|
qmljs: avoid linking to files in the build directory
cmake creates a consistent uri structure in the build directory.
We use that as import path, but when we find a type in them we should
refer back to the original file, as editing those is dangerous because
any edit are lost with the next build.
To find the original file we use the qrc, as the qrc path is mostly
the same of as the uri path.
It is possible to add prefixes which would make an exact match fail,
so we compare the paths from the right to the left and find the
longest match.
To acheive this:
* QrcParser keeps a reversedResources, so the match from right can be
done efficiently, and provides a longestReverseMatches method
* the model manager keeps a list of all common prefixes of the
application paths (build directories), and identify all files in
build directories
* the method fileToSource identifies the files in the build directory
and tries to find the corresponding source file, warning if he
cannot find it
* fileToSource is used for follow Symbol and find usages
We could use fileToSource much more aggressively, to use to in editor
content for the files in the build directory, increasing the
consistency, but that is a more dangerous change for later.
Fixes: QTCREATORBUG-27173
Change-Id: Iea61b9825e5f6e433a7390cf2de9564b792458a5
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-06-17 15:46:52 +02:00
|
|
|
{
|
|
|
|
QList<Utils::FilePath> res;
|
2022-06-20 12:35:13 +02:00
|
|
|
for (const Utils::FilePath &path : applicationDirectories) {
|
qmljs: avoid linking to files in the build directory
cmake creates a consistent uri structure in the build directory.
We use that as import path, but when we find a type in them we should
refer back to the original file, as editing those is dangerous because
any edit are lost with the next build.
To find the original file we use the qrc, as the qrc path is mostly
the same of as the uri path.
It is possible to add prefixes which would make an exact match fail,
so we compare the paths from the right to the left and find the
longest match.
To acheive this:
* QrcParser keeps a reversedResources, so the match from right can be
done efficiently, and provides a longestReverseMatches method
* the model manager keeps a list of all common prefixes of the
application paths (build directories), and identify all files in
build directories
* the method fileToSource identifies the files in the build directory
and tries to find the corresponding source file, warning if he
cannot find it
* fileToSource is used for follow Symbol and find usages
We could use fileToSource much more aggressively, to use to in editor
content for the files in the build directory, increasing the
consistency, but that is a more dangerous change for later.
Fixes: QTCREATORBUG-27173
Change-Id: Iea61b9825e5f6e433a7390cf2de9564b792458a5
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-06-17 15:46:52 +02:00
|
|
|
Utils::FilePath generatedQrcDir = path.pathAppended(".rcc");
|
|
|
|
if (generatedQrcDir.isReadableDir()) {
|
|
|
|
for (const Utils::FilePath & qrcPath: generatedQrcDir.dirEntries(FileFilter(QStringList({QStringLiteral(u"*.qrc")}), QDir::Files)))
|
|
|
|
res.append(qrcPath.canonicalPath());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2019-10-23 14:03:13 +02:00
|
|
|
void ModelManagerInterface::iterateQrcFiles(
|
|
|
|
ProjectExplorer::Project *project, QrcResourceSelector resources,
|
|
|
|
const std::function<void(QrcParser::ConstPtr)> &callback)
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
|
|
|
QList<ProjectInfo> pInfos;
|
2016-03-18 17:43:33 +01:00
|
|
|
if (project) {
|
2014-01-23 14:28:31 +01:00
|
|
|
pInfos.append(projectInfo(project));
|
2016-03-18 17:43:33 +01:00
|
|
|
} else {
|
2014-01-23 14:28:31 +01:00
|
|
|
pInfos = projectInfos();
|
2016-03-18 17:43:33 +01:00
|
|
|
if (resources == ActiveQrcResources) // make the result predictable
|
2016-08-03 23:29:58 +03:00
|
|
|
Utils::sort(pInfos, &pInfoLessThanActive);
|
2016-03-18 17:43:33 +01:00
|
|
|
else
|
2016-08-03 23:29:58 +03:00
|
|
|
Utils::sort(pInfos, &pInfoLessThanAll);
|
2016-03-18 17:43:33 +01:00
|
|
|
}
|
2014-01-23 14:28:31 +01:00
|
|
|
|
qmljs: avoid linking to files in the build directory
cmake creates a consistent uri structure in the build directory.
We use that as import path, but when we find a type in them we should
refer back to the original file, as editing those is dangerous because
any edit are lost with the next build.
To find the original file we use the qrc, as the qrc path is mostly
the same of as the uri path.
It is possible to add prefixes which would make an exact match fail,
so we compare the paths from the right to the left and find the
longest match.
To acheive this:
* QrcParser keeps a reversedResources, so the match from right can be
done efficiently, and provides a longestReverseMatches method
* the model manager keeps a list of all common prefixes of the
application paths (build directories), and identify all files in
build directories
* the method fileToSource identifies the files in the build directory
and tries to find the corresponding source file, warning if he
cannot find it
* fileToSource is used for follow Symbol and find usages
We could use fileToSource much more aggressively, to use to in editor
content for the files in the build directory, increasing the
consistency, but that is a more dangerous change for later.
Fixes: QTCREATORBUG-27173
Change-Id: Iea61b9825e5f6e433a7390cf2de9564b792458a5
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-06-17 15:46:52 +02:00
|
|
|
QSet<Utils::FilePath> pathsChecked;
|
2019-10-23 14:03:13 +02:00
|
|
|
for (const ModelManagerInterface::ProjectInfo &pInfo : qAsConst(pInfos)) {
|
2022-06-20 12:35:13 +02:00
|
|
|
QList<Utils::FilePath> qrcFilePaths;
|
2014-01-23 14:28:31 +01:00
|
|
|
if (resources == ActiveQrcResources)
|
|
|
|
qrcFilePaths = pInfo.activeResourceFiles;
|
|
|
|
else
|
|
|
|
qrcFilePaths = pInfo.allResourceFiles;
|
qmljs: avoid linking to files in the build directory
cmake creates a consistent uri structure in the build directory.
We use that as import path, but when we find a type in them we should
refer back to the original file, as editing those is dangerous because
any edit are lost with the next build.
To find the original file we use the qrc, as the qrc path is mostly
the same of as the uri path.
It is possible to add prefixes which would make an exact match fail,
so we compare the paths from the right to the left and find the
longest match.
To acheive this:
* QrcParser keeps a reversedResources, so the match from right can be
done efficiently, and provides a longestReverseMatches method
* the model manager keeps a list of all common prefixes of the
application paths (build directories), and identify all files in
build directories
* the method fileToSource identifies the files in the build directory
and tries to find the corresponding source file, warning if he
cannot find it
* fileToSource is used for follow Symbol and find usages
We could use fileToSource much more aggressively, to use to in editor
content for the files in the build directory, increasing the
consistency, but that is a more dangerous change for later.
Fixes: QTCREATORBUG-27173
Change-Id: Iea61b9825e5f6e433a7390cf2de9564b792458a5
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-06-17 15:46:52 +02:00
|
|
|
for (const Utils::FilePath &p : generatedQrc(pInfo.applicationDirectories))
|
2022-06-20 12:35:13 +02:00
|
|
|
qrcFilePaths.append(p);
|
|
|
|
for (const Utils::FilePath &qrcFilePath : qAsConst(qrcFilePaths)) {
|
2014-01-23 14:28:31 +01:00
|
|
|
if (pathsChecked.contains(qrcFilePath))
|
|
|
|
continue;
|
|
|
|
pathsChecked.insert(qrcFilePath);
|
qmljs: avoid linking to files in the build directory
cmake creates a consistent uri structure in the build directory.
We use that as import path, but when we find a type in them we should
refer back to the original file, as editing those is dangerous because
any edit are lost with the next build.
To find the original file we use the qrc, as the qrc path is mostly
the same of as the uri path.
It is possible to add prefixes which would make an exact match fail,
so we compare the paths from the right to the left and find the
longest match.
To acheive this:
* QrcParser keeps a reversedResources, so the match from right can be
done efficiently, and provides a longestReverseMatches method
* the model manager keeps a list of all common prefixes of the
application paths (build directories), and identify all files in
build directories
* the method fileToSource identifies the files in the build directory
and tries to find the corresponding source file, warning if he
cannot find it
* fileToSource is used for follow Symbol and find usages
We could use fileToSource much more aggressively, to use to in editor
content for the files in the build directory, increasing the
consistency, but that is a more dangerous change for later.
Fixes: QTCREATORBUG-27173
Change-Id: Iea61b9825e5f6e433a7390cf2de9564b792458a5
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-06-17 15:46:52 +02:00
|
|
|
QrcParser::ConstPtr qrcFile = m_qrcCache.parsedPath(qrcFilePath.toString());
|
2014-01-23 14:28:31 +01:00
|
|
|
if (qrcFile.isNull())
|
|
|
|
continue;
|
2016-03-18 17:43:33 +01:00
|
|
|
callback(qrcFile);
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
}
|
2016-03-18 17:43:33 +01:00
|
|
|
}
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
QStringList ModelManagerInterface::qrcPathsForFile(const Utils::FilePath &file,
|
|
|
|
const QLocale *locale,
|
2016-03-18 17:43:33 +01:00
|
|
|
ProjectExplorer::Project *project,
|
|
|
|
QrcResourceSelector resources)
|
|
|
|
{
|
|
|
|
QStringList res;
|
2019-10-23 14:03:13 +02:00
|
|
|
iterateQrcFiles(project, resources, [&](const QrcParser::ConstPtr &qrcFile) {
|
2022-06-20 12:35:13 +02:00
|
|
|
qrcFile->collectResourceFilesForSourceFile(file.toString(), &res, locale);
|
2016-03-18 17:43:33 +01:00
|
|
|
});
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
QStringList ModelManagerInterface::filesAtQrcPath(const QString &path, const QLocale *locale,
|
|
|
|
ProjectExplorer::Project *project,
|
|
|
|
QrcResourceSelector resources)
|
|
|
|
{
|
|
|
|
QString normPath = QrcParser::normalizedQrcFilePath(path);
|
|
|
|
QStringList res;
|
2019-10-23 14:03:13 +02:00
|
|
|
iterateQrcFiles(project, resources, [&](const QrcParser::ConstPtr &qrcFile) {
|
2016-03-18 17:43:33 +01:00
|
|
|
qrcFile->collectFilesAtPath(normPath, &res, locale);
|
|
|
|
});
|
2014-01-23 14:28:31 +01:00
|
|
|
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;
|
2019-10-23 14:03:13 +02:00
|
|
|
iterateQrcFiles(project, resources, [&](const QrcParser::ConstPtr &qrcFile) {
|
2016-03-18 17:43:33 +01:00
|
|
|
qrcFile->collectFilesInPath(normPath, &res, addDirs, locale);
|
|
|
|
});
|
2014-01-23 14:28:31 +01:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
QList<ModelManagerInterface::ProjectInfo> ModelManagerInterface::projectInfos() const
|
|
|
|
{
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
|
|
|
return m_projects.values();
|
|
|
|
}
|
|
|
|
|
2019-10-23 14:03:13 +02:00
|
|
|
bool ModelManagerInterface::containsProject(ProjectExplorer::Project *project) const
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
|
|
|
QMutexLocker locker(&m_mutex);
|
2019-10-23 14:03:13 +02:00
|
|
|
return m_projects.contains(project);
|
|
|
|
}
|
2014-01-23 14:28:31 +01:00
|
|
|
|
2019-10-23 14:03:13 +02:00
|
|
|
ModelManagerInterface::ProjectInfo ModelManagerInterface::projectInfo(
|
|
|
|
ProjectExplorer::Project *project) const
|
|
|
|
{
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
return m_projects.value(project);
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void ModelManagerInterface::updateProjectInfo(const ProjectInfo &pinfo, ProjectExplorer::Project *p)
|
|
|
|
{
|
2019-10-23 14:03:13 +02:00
|
|
|
if (pinfo.project.isNull() || !p || m_indexerDisabled)
|
2014-01-23 14:28:31 +01:00
|
|
|
return;
|
|
|
|
|
|
|
|
Snapshot snapshot;
|
|
|
|
ProjectInfo oldInfo;
|
|
|
|
{
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
oldInfo = m_projects.value(p);
|
|
|
|
m_projects.insert(p, pinfo);
|
2014-06-30 11:56:28 +02:00
|
|
|
if (p == m_defaultProject)
|
|
|
|
m_defaultProjectInfo = pinfo;
|
2014-02-13 11:11:40 +01:00
|
|
|
snapshot = m_validSnapshot;
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (oldInfo.qmlDumpPath != pinfo.qmlDumpPath
|
|
|
|
|| oldInfo.qmlDumpEnvironment != pinfo.qmlDumpEnvironment) {
|
|
|
|
m_pluginDumper->scheduleRedumpPlugins();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
updateImportPaths();
|
|
|
|
|
|
|
|
// remove files that are no longer in the project and have been deleted
|
2022-06-20 12:35:13 +02:00
|
|
|
QList<Utils::FilePath> deletedFiles;
|
|
|
|
for (const Utils::FilePath &oldFile : qAsConst(oldInfo.sourceFiles)) {
|
|
|
|
if (snapshot.document(oldFile) && !pinfo.sourceFiles.contains(oldFile)
|
|
|
|
&& !oldFile.exists()) {
|
2014-01-23 14:28:31 +01:00
|
|
|
deletedFiles += oldFile;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
removeFiles(deletedFiles);
|
2022-06-20 12:35:13 +02:00
|
|
|
for (const Utils::FilePath &oldFile : qAsConst(deletedFiles))
|
2014-01-23 14:28:31 +01:00
|
|
|
m_fileToProject.remove(oldFile, p);
|
|
|
|
|
|
|
|
// parse any files not yet in the snapshot
|
2022-06-20 12:35:13 +02:00
|
|
|
QList<Utils::FilePath> newFiles;
|
|
|
|
for (const Utils::FilePath &file : qAsConst(pinfo.sourceFiles)) {
|
2020-05-20 12:47:09 +03:00
|
|
|
if (!m_fileToProject.contains(file, p))
|
|
|
|
m_fileToProject.insert(file, p);
|
2014-01-23 14:28:31 +01:00
|
|
|
if (!snapshot.document(file))
|
|
|
|
newFiles += file;
|
|
|
|
}
|
2020-05-20 12:47:09 +03:00
|
|
|
|
2014-12-02 13:24:25 +01:00
|
|
|
updateSourceFiles(newFiles, false);
|
2014-01-23 14:28:31 +01:00
|
|
|
|
|
|
|
// update qrc cache
|
2016-10-24 19:30:24 +02:00
|
|
|
m_qrcContents = pinfo.resourceFileContents;
|
2022-06-20 12:35:13 +02:00
|
|
|
for (const Utils::FilePath &newQrc : qAsConst(pinfo.allResourceFiles))
|
|
|
|
m_qrcCache.addPath(newQrc.toString(), m_qrcContents.value(newQrc));
|
qmljs: avoid linking to files in the build directory
cmake creates a consistent uri structure in the build directory.
We use that as import path, but when we find a type in them we should
refer back to the original file, as editing those is dangerous because
any edit are lost with the next build.
To find the original file we use the qrc, as the qrc path is mostly
the same of as the uri path.
It is possible to add prefixes which would make an exact match fail,
so we compare the paths from the right to the left and find the
longest match.
To acheive this:
* QrcParser keeps a reversedResources, so the match from right can be
done efficiently, and provides a longestReverseMatches method
* the model manager keeps a list of all common prefixes of the
application paths (build directories), and identify all files in
build directories
* the method fileToSource identifies the files in the build directory
and tries to find the corresponding source file, warning if he
cannot find it
* fileToSource is used for follow Symbol and find usages
We could use fileToSource much more aggressively, to use to in editor
content for the files in the build directory, increasing the
consistency, but that is a more dangerous change for later.
Fixes: QTCREATORBUG-27173
Change-Id: Iea61b9825e5f6e433a7390cf2de9564b792458a5
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-06-17 15:46:52 +02:00
|
|
|
for (const Utils::FilePath &newQrc : generatedQrc(pinfo.applicationDirectories))
|
2022-06-20 12:35:13 +02:00
|
|
|
m_qrcCache.addPath(newQrc.toString(), m_qrcContents.value(newQrc));
|
|
|
|
for (const Utils::FilePath &oldQrc : qAsConst(oldInfo.allResourceFiles))
|
|
|
|
m_qrcCache.removePath(oldQrc.toString());
|
2014-01-23 14:28:31 +01:00
|
|
|
|
2019-10-23 14:03:13 +02:00
|
|
|
m_pluginDumper->loadBuiltinTypes(pinfo);
|
2014-01-23 14:28:31 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-26 23:59:36 +03:00
|
|
|
/*!
|
|
|
|
Returns project info with summarized info for \a path
|
|
|
|
|
|
|
|
\note Project pointer will be empty
|
|
|
|
*/
|
2019-10-23 14:03:13 +02:00
|
|
|
ModelManagerInterface::ProjectInfo ModelManagerInterface::projectInfoForPath(
|
2022-06-20 12:35:13 +02:00
|
|
|
const Utils::FilePath &path) const
|
2015-04-26 23:59:36 +03:00
|
|
|
{
|
|
|
|
ProjectInfo res;
|
2019-10-23 14:03:13 +02:00
|
|
|
const auto allProjectInfos = allProjectInfosForPath(path);
|
|
|
|
for (const ProjectInfo &pInfo : allProjectInfos) {
|
2020-09-29 09:55:41 +02:00
|
|
|
if (res.qtQmlPath.isEmpty()) {
|
2015-04-26 23:59:36 +03:00
|
|
|
res.qtQmlPath = pInfo.qtQmlPath;
|
2020-09-29 09:55:41 +02:00
|
|
|
res.qtVersionString = pInfo.qtVersionString;
|
|
|
|
}
|
2019-10-22 16:29:40 +02:00
|
|
|
res.applicationDirectories.append(pInfo.applicationDirectories);
|
2019-10-23 14:03:13 +02:00
|
|
|
for (const auto &importPath : pInfo.importPaths)
|
|
|
|
res.importPaths.maybeInsert(importPath);
|
2021-03-19 15:49:26 +01:00
|
|
|
auto end = pInfo.moduleMappings.cend();
|
|
|
|
for (auto it = pInfo.moduleMappings.cbegin(); it != end; ++it)
|
|
|
|
res.moduleMappings.insert(it.key(), it.value());
|
2015-04-26 23:59:36 +03:00
|
|
|
}
|
2019-10-22 16:29:40 +02:00
|
|
|
res.applicationDirectories = Utils::filteredUnique(res.applicationDirectories);
|
2015-04-26 23:59:36 +03:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
Returns list of project infos for \a path
|
|
|
|
*/
|
2019-10-23 14:03:13 +02:00
|
|
|
QList<ModelManagerInterface::ProjectInfo> ModelManagerInterface::allProjectInfosForPath(
|
2022-06-20 12:35:13 +02:00
|
|
|
const Utils::FilePath &path) const
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
2014-04-11 23:07:52 +02:00
|
|
|
QList<ProjectExplorer::Project *> projects;
|
|
|
|
{
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
projects = m_fileToProject.values(path);
|
2022-06-20 12:35:13 +02:00
|
|
|
if (projects.isEmpty())
|
|
|
|
projects = m_fileToProject.values(path.canonicalPath());
|
2014-04-11 23:07:52 +02:00
|
|
|
}
|
|
|
|
QList<ProjectInfo> infos;
|
2019-10-23 14:03:13 +02:00
|
|
|
for (ProjectExplorer::Project *project : qAsConst(projects)) {
|
2014-04-11 23:07:52 +02:00
|
|
|
ProjectInfo info = projectInfo(project);
|
2019-10-23 14:03:13 +02:00
|
|
|
if (!info.project.isNull())
|
2014-04-11 23:07:52 +02:00
|
|
|
infos.append(info);
|
|
|
|
}
|
2021-03-17 11:32:18 +01:00
|
|
|
if (infos.isEmpty()) {
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
return { m_defaultProjectInfo };
|
|
|
|
}
|
2014-04-11 23:07:52 +02:00
|
|
|
std::sort(infos.begin(), infos.end(), &pInfoLessThanImports);
|
2015-04-26 23:59:36 +03:00
|
|
|
return infos;
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void ModelManagerInterface::emitDocumentChangedOnDisk(Document::Ptr doc)
|
2019-10-23 14:03:13 +02:00
|
|
|
{
|
|
|
|
emit documentChangedOnDisk(std::move(doc));
|
|
|
|
}
|
2014-01-23 14:28:31 +01:00
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
void ModelManagerInterface::updateQrcFile(const Utils::FilePath &path)
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
2022-06-20 12:35:13 +02:00
|
|
|
m_qrcCache.updatePath(path.toString(), m_qrcContents.value(path));
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
2019-10-23 14:03:13 +02:00
|
|
|
void ModelManagerInterface::updateDocument(const Document::Ptr &doc)
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
|
|
|
{
|
|
|
|
QMutexLocker locker(&m_mutex);
|
2014-02-13 11:11:40 +01:00
|
|
|
m_validSnapshot.insert(doc);
|
|
|
|
m_newestSnapshot.insert(doc, true);
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
emit documentUpdated(doc);
|
|
|
|
}
|
|
|
|
|
2021-10-12 18:00:17 +02:00
|
|
|
void ModelManagerInterface::updateLibraryInfo(const FilePath &path, const LibraryInfo &info)
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
|
|
|
if (!info.pluginTypeInfoError().isEmpty())
|
2014-07-22 17:05:28 +02:00
|
|
|
qCDebug(qmljsLog) << "Dumping errors for " << path << ":" << info.pluginTypeInfoError();
|
2014-01-23 14:28:31 +01:00
|
|
|
|
|
|
|
{
|
|
|
|
QMutexLocker locker(&m_mutex);
|
2022-06-20 12:35:13 +02:00
|
|
|
m_validSnapshot.insertLibraryInfo(path, info);
|
|
|
|
m_newestSnapshot.insertLibraryInfo(path, info);
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
// only emit if we got new useful information
|
|
|
|
if (info.isValid())
|
2022-06-20 12:35:13 +02:00
|
|
|
emit libraryInfoUpdated(path, info);
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
static QList<Utils::FilePath> filesInDirectoryForLanguages(const Utils::FilePath &path,
|
|
|
|
const QList<Dialect> &languages)
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
|
|
|
const QStringList pattern = ModelManagerInterface::globPatternsForLanguages(languages);
|
2022-06-20 12:35:13 +02:00
|
|
|
QList<Utils::FilePath> files;
|
2014-01-23 14:28:31 +01:00
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
for (const Utils::FilePath &p : path.dirEntries(FileFilter(pattern, QDir::Files)))
|
|
|
|
files.append(p.absoluteFilePath());
|
2014-01-23 14:28:31 +01:00
|
|
|
|
|
|
|
return files;
|
|
|
|
}
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
static void findNewImplicitImports(const Document::Ptr &doc,
|
|
|
|
const Snapshot &snapshot,
|
|
|
|
QList<Utils::FilePath> *importedFiles,
|
|
|
|
QSet<Utils::FilePath> *scannedPaths)
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
|
|
|
// 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()) {
|
2019-10-23 14:03:13 +02:00
|
|
|
if (!scannedPaths->contains(doc->path())) {
|
2014-01-23 14:28:31 +01:00
|
|
|
*importedFiles += filesInDirectoryForLanguages(doc->path(),
|
2019-10-23 14:03:13 +02:00
|
|
|
doc->language().companionLanguages());
|
2014-01-23 14:28:31 +01:00
|
|
|
scannedPaths->insert(doc->path());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
static void findNewFileImports(const Document::Ptr &doc,
|
|
|
|
const Snapshot &snapshot,
|
|
|
|
QList<Utils::FilePath> *importedFiles,
|
|
|
|
QSet<Utils::FilePath> *scannedPaths)
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
|
|
|
// scan files and directories that are explicitly imported
|
2019-10-23 14:03:13 +02:00
|
|
|
const auto imports = doc->bind()->imports();
|
|
|
|
for (const ImportInfo &import : imports) {
|
2014-01-23 14:28:31 +01:00
|
|
|
const QString &importName = import.path();
|
2022-06-20 12:35:13 +02:00
|
|
|
Utils::FilePath importPath = Utils::FilePath::fromString(importName);
|
2014-01-23 14:28:31 +01:00
|
|
|
if (import.type() == ImportType::File) {
|
2022-06-20 12:35:13 +02:00
|
|
|
if (!snapshot.document(importPath))
|
|
|
|
*importedFiles += importPath;
|
2014-01-23 14:28:31 +01:00
|
|
|
} else if (import.type() == ImportType::Directory) {
|
2022-06-20 12:35:13 +02:00
|
|
|
if (snapshot.documentsInDirectory(importPath).isEmpty()) {
|
|
|
|
if (!scannedPaths->contains(importPath)) {
|
|
|
|
*importedFiles
|
|
|
|
+= filesInDirectoryForLanguages(importPath,
|
|
|
|
doc->language().companionLanguages());
|
|
|
|
scannedPaths->insert(importPath);
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (import.type() == ImportType::QrcFile) {
|
2019-10-23 14:03:13 +02:00
|
|
|
const QStringList importPaths
|
|
|
|
= ModelManagerInterface::instance()->filesAtQrcPath(importName);
|
2022-06-20 12:35:13 +02:00
|
|
|
for (const QString &importStr : importPaths) {
|
|
|
|
Utils::FilePath importPath = Utils::FilePath::fromString(importStr);
|
2019-10-23 14:03:13 +02:00
|
|
|
if (!snapshot.document(importPath))
|
2014-01-23 14:28:31 +01:00
|
|
|
*importedFiles += importPath;
|
|
|
|
}
|
|
|
|
} else if (import.type() == ImportType::QrcDirectory) {
|
2019-10-23 14:03:13 +02:00
|
|
|
const QMap<QString, QStringList> files
|
|
|
|
= ModelManagerInterface::instance()->filesInQrcPath(importName);
|
|
|
|
for (auto qrc = files.cbegin(), end = files.cend(); qrc != end; ++qrc) {
|
2022-06-20 12:35:13 +02:00
|
|
|
if (ModelManagerInterface::guessLanguageOfFile(
|
|
|
|
Utils::FilePath::fromString(qrc.key()))
|
|
|
|
.isQmlLikeOrJsLanguage()) {
|
2019-10-23 14:03:13 +02:00
|
|
|
for (const QString &sourceFile : qrc.value()) {
|
2022-06-20 12:35:13 +02:00
|
|
|
auto sourceFilePath = Utils::FilePath::fromString(sourceFile);
|
|
|
|
if (!snapshot.document(sourceFilePath))
|
|
|
|
*importedFiles += sourceFilePath;
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-22 16:29:40 +02:00
|
|
|
enum class LibraryStatus {
|
|
|
|
Accepted,
|
|
|
|
Rejected,
|
|
|
|
Unknown
|
|
|
|
};
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
static LibraryStatus libraryStatus(const FilePath &path,
|
|
|
|
const Snapshot &snapshot,
|
|
|
|
QSet<Utils::FilePath> *newLibraries)
|
2019-10-22 16:29:40 +02:00
|
|
|
{
|
|
|
|
if (path.isEmpty())
|
|
|
|
return LibraryStatus::Rejected;
|
|
|
|
// if we know there is a library, done
|
|
|
|
const LibraryInfo &existingInfo = snapshot.libraryInfo(path);
|
|
|
|
if (existingInfo.isValid())
|
|
|
|
return LibraryStatus::Accepted;
|
2022-06-20 12:35:13 +02:00
|
|
|
if (newLibraries->contains(path))
|
2019-10-22 16:29:40 +02:00
|
|
|
return LibraryStatus::Accepted;
|
|
|
|
// if we looked at the path before, done
|
|
|
|
return existingInfo.wasScanned()
|
|
|
|
? LibraryStatus::Rejected
|
|
|
|
: LibraryStatus::Unknown;
|
|
|
|
}
|
|
|
|
|
2021-10-12 18:00:17 +02:00
|
|
|
static bool findNewQmlApplicationInPath(const FilePath &path,
|
2019-10-22 16:29:40 +02:00
|
|
|
const Snapshot &snapshot,
|
|
|
|
ModelManagerInterface *modelManager,
|
2022-06-20 12:35:13 +02:00
|
|
|
QSet<FilePath> *newLibraries)
|
2019-10-22 16:29:40 +02:00
|
|
|
{
|
|
|
|
switch (libraryStatus(path, snapshot, newLibraries)) {
|
|
|
|
case LibraryStatus::Accepted: return true;
|
|
|
|
case LibraryStatus::Rejected: return false;
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
FilePath qmltypesFile;
|
2020-03-26 12:17:03 +01:00
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
QList<Utils::FilePath> qmlTypes = path.dirEntries(
|
|
|
|
FileFilter(QStringList{"*.qmltypes"}, QDir::Files));
|
2020-03-26 12:17:03 +01:00
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
if (qmlTypes.isEmpty())
|
2019-10-22 16:29:40 +02:00
|
|
|
return false;
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
qmltypesFile = qmlTypes.first();
|
2020-03-26 12:17:03 +01:00
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
LibraryInfo libraryInfo = LibraryInfo(qmltypesFile.toString());
|
|
|
|
const Utils::FilePath libraryPath = path.absolutePath();
|
2019-10-22 16:29:40 +02:00
|
|
|
newLibraries->insert(libraryPath);
|
|
|
|
modelManager->updateLibraryInfo(path, libraryInfo);
|
2022-06-20 12:35:13 +02:00
|
|
|
modelManager->loadPluginTypes(libraryPath.canonicalPath(), libraryPath, QString(), QString());
|
2019-10-22 16:29:40 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
static bool findNewQmlLibraryInPath(const Utils::FilePath &path,
|
2014-01-23 14:28:31 +01:00
|
|
|
const Snapshot &snapshot,
|
|
|
|
ModelManagerInterface *modelManager,
|
2022-06-20 12:35:13 +02:00
|
|
|
QList<Utils::FilePath> *importedFiles,
|
|
|
|
QSet<Utils::FilePath> *scannedPaths,
|
|
|
|
QSet<Utils::FilePath> *newLibraries,
|
2014-01-23 14:28:31 +01:00
|
|
|
bool ignoreMissing)
|
|
|
|
{
|
2022-06-20 12:35:13 +02:00
|
|
|
switch (libraryStatus(path, snapshot, newLibraries)) {
|
2019-10-22 16:29:40 +02:00
|
|
|
case LibraryStatus::Accepted: return true;
|
|
|
|
case LibraryStatus::Rejected: return false;
|
|
|
|
default: break;
|
|
|
|
}
|
2014-01-23 14:28:31 +01:00
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
Utils::FilePath qmldirFile = path.pathAppended(QLatin1String("qmldir"));
|
2014-01-23 14:28:31 +01:00
|
|
|
if (!qmldirFile.exists()) {
|
|
|
|
if (!ignoreMissing) {
|
|
|
|
LibraryInfo libraryInfo(LibraryInfo::NotFound);
|
2022-06-20 12:35:13 +02:00
|
|
|
modelManager->updateLibraryInfo(path, libraryInfo);
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Utils::HostOsInfo::isWindowsHost()) {
|
|
|
|
// QTCREATORBUG-3402 - be case sensitive even here?
|
|
|
|
}
|
|
|
|
|
|
|
|
// found a new library!
|
2022-06-20 12:35:13 +02:00
|
|
|
if (!qmldirFile.isReadableFile())
|
2017-04-06 12:42:23 +02:00
|
|
|
return false;
|
2022-06-20 12:35:13 +02:00
|
|
|
QString qmldirData = QString::fromUtf8(qmldirFile.fileContents());
|
2014-01-23 14:28:31 +01:00
|
|
|
|
|
|
|
QmlDirParser qmldirParser;
|
|
|
|
qmldirParser.parse(qmldirData);
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
const Utils::FilePath libraryPath = qmldirFile.absolutePath();
|
2014-01-23 14:28:31 +01:00
|
|
|
newLibraries->insert(libraryPath);
|
2022-06-20 12:35:13 +02:00
|
|
|
modelManager->updateLibraryInfo(libraryPath, LibraryInfo(qmldirParser));
|
|
|
|
modelManager->loadPluginTypes(libraryPath.canonicalPath(), libraryPath, QString(), QString());
|
2014-01-23 14:28:31 +01:00
|
|
|
|
|
|
|
// scan the qml files in the library
|
2019-10-23 14:03:13 +02:00
|
|
|
const auto components = qmldirParser.components();
|
|
|
|
for (const QmlDirParser::Component &component : components) {
|
|
|
|
if (!component.fileName.isEmpty()) {
|
2022-06-20 12:35:13 +02:00
|
|
|
const FilePath componentFile = path.pathAppended(component.fileName);
|
|
|
|
const FilePath path = componentFile.absolutePath().cleanPath();
|
2019-10-23 14:03:13 +02:00
|
|
|
if (!scannedPaths->contains(path)) {
|
|
|
|
*importedFiles += filesInDirectoryForLanguages(path, Dialect(Dialect::AnyLanguage)
|
|
|
|
.companionLanguages());
|
2014-01-23 14:28:31 +01:00
|
|
|
scannedPaths->insert(path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
static FilePath modulePath(const ImportInfo &import, const QList<FilePath> &paths)
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
2017-09-28 16:24:13 +02:00
|
|
|
if (!import.version().isValid())
|
2022-06-20 12:35:13 +02:00
|
|
|
return FilePath();
|
2021-01-11 14:09:51 +01:00
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
const QList<FilePath> modPaths = modulePaths(import.name(), import.version().toString(), paths);
|
2021-01-11 14:09:51 +01:00
|
|
|
return modPaths.value(0); // first is best match
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
static void findNewLibraryImports(const Document::Ptr &doc,
|
|
|
|
const Snapshot &snapshot,
|
|
|
|
ModelManagerInterface *modelManager,
|
|
|
|
QList<FilePath> *importedFiles,
|
|
|
|
QSet<Utils::FilePath> *scannedPaths,
|
|
|
|
QSet<Utils::FilePath> *newLibraries)
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
|
|
|
// scan current dir
|
|
|
|
findNewQmlLibraryInPath(doc->path(), snapshot, modelManager,
|
|
|
|
importedFiles, scannedPaths, newLibraries, false);
|
|
|
|
|
|
|
|
// scan dir and lib imports
|
2022-06-20 12:35:13 +02:00
|
|
|
const QList<FilePath> importPaths = modelManager->importPathsNames();
|
2019-10-23 14:03:13 +02:00
|
|
|
const auto imports = doc->bind()->imports();
|
|
|
|
for (const ImportInfo &import : imports) {
|
|
|
|
switch (import.type()) {
|
|
|
|
case ImportType::Directory:
|
2022-06-20 12:35:13 +02:00
|
|
|
findNewQmlLibraryInPath(Utils::FilePath::fromString(import.path()),
|
|
|
|
snapshot,
|
|
|
|
modelManager,
|
|
|
|
importedFiles,
|
|
|
|
scannedPaths,
|
|
|
|
newLibraries,
|
|
|
|
false);
|
2019-10-23 14:03:13 +02:00
|
|
|
break;
|
|
|
|
case ImportType::Library:
|
|
|
|
findNewQmlLibraryInPath(modulePath(import, importPaths), snapshot, modelManager,
|
|
|
|
importedFiles, scannedPaths, newLibraries, false);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
void ModelManagerInterface::parseLoop(QSet<Utils::FilePath> &scannedPaths,
|
|
|
|
QSet<Utils::FilePath> &newLibraries,
|
2019-10-23 14:03:13 +02:00
|
|
|
const WorkingCopy &workingCopy,
|
2022-06-20 12:35:13 +02:00
|
|
|
QList<Utils::FilePath> files,
|
2019-10-23 14:03:13 +02:00
|
|
|
ModelManagerInterface *modelManager,
|
|
|
|
Dialect mainLanguage,
|
|
|
|
bool emitDocChangedOnDisk,
|
|
|
|
const std::function<bool(qreal)> &reportProgress)
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
|
|
|
for (int i = 0; i < files.size(); ++i) {
|
|
|
|
if (!reportProgress(qreal(i) / files.size()))
|
|
|
|
return;
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
const Utils::FilePath fileName = files.at(i);
|
2014-01-23 14:28:31 +01:00
|
|
|
|
2014-07-22 19:06:44 +02:00
|
|
|
Dialect language = guessLanguageOfFile(fileName);
|
|
|
|
if (language == Dialect::NoLanguage) {
|
2014-01-23 14:28:31 +01:00
|
|
|
if (fileName.endsWith(QLatin1String(".qrc")))
|
|
|
|
modelManager->updateQrcFile(fileName);
|
|
|
|
continue;
|
|
|
|
}
|
2014-07-22 19:06:44 +02:00
|
|
|
if (language == Dialect::Qml
|
2018-02-19 12:26:51 +01:00
|
|
|
&& (mainLanguage == Dialect::QmlQtQuick2))
|
2014-01-23 14:28:31 +01:00
|
|
|
language = mainLanguage;
|
2014-10-13 16:46:30 +02:00
|
|
|
if (language == Dialect::Qml && mainLanguage == Dialect::QmlQtQuick2Ui)
|
|
|
|
language = Dialect::QmlQtQuick2;
|
2014-11-20 16:13:32 +01:00
|
|
|
if (language == Dialect::QmlTypeInfo || language == Dialect::QmlProject)
|
|
|
|
continue;
|
2014-01-23 14:28:31 +01:00
|
|
|
QString contents;
|
|
|
|
int documentRevision = 0;
|
|
|
|
|
|
|
|
if (workingCopy.contains(fileName)) {
|
|
|
|
QPair<QString, int> entry = workingCopy.get(fileName);
|
|
|
|
contents = entry.first;
|
|
|
|
documentRevision = entry.second;
|
|
|
|
} else {
|
2022-06-20 12:35:13 +02:00
|
|
|
if (fileName.isReadableFile()) {
|
|
|
|
QTextStream ins(fileName.fileContents());
|
2014-01-23 14:28:31 +01:00
|
|
|
contents = ins.readAll();
|
2017-12-06 16:01:52 +01:00
|
|
|
} else {
|
|
|
|
continue;
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Document::MutablePtr doc = Document::create(fileName, language);
|
|
|
|
doc->setEditorRevision(documentRevision);
|
|
|
|
doc->setSource(contents);
|
|
|
|
doc->parse();
|
|
|
|
|
2021-04-30 16:52:03 +02:00
|
|
|
#ifdef WITH_TESTS
|
2022-06-10 11:00:14 +02:00
|
|
|
if (ExtensionSystem::PluginManager::instance() // we might run as an auto-test
|
|
|
|
&& ExtensionSystem::PluginManager::isScenarioRunning("TestModelManagerInterface")) {
|
2021-04-30 16:52:03 +02:00
|
|
|
ExtensionSystem::PluginManager::waitForScenarioFullyInitialized();
|
|
|
|
if (ExtensionSystem::PluginManager::finishScenario()) {
|
|
|
|
qDebug() << "Point 1: Shutdown triggered";
|
|
|
|
QThread::currentThread()->sleep(2);
|
|
|
|
qDebug() << "Point 3: If Point 2 was already reached, expect a crash now";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
2014-01-23 14:28:31 +01:00
|
|
|
// get list of referenced files not yet in snapshot or in directories already scanned
|
2022-06-20 12:35:13 +02:00
|
|
|
QList<Utils::FilePath> importedFiles;
|
2021-08-13 10:35:49 +02:00
|
|
|
|
|
|
|
// update snapshot. requires synchronization, but significantly reduces amount of file
|
|
|
|
// system queries for library imports because queries are cached in libraryInfo
|
|
|
|
{
|
|
|
|
// Make sure the snapshot is destroyed before updateDocument, so that we don't trigger
|
|
|
|
// the copy-on-write mechanism on its internals.
|
|
|
|
const Snapshot snapshot = modelManager->snapshot();
|
|
|
|
|
|
|
|
findNewImplicitImports(doc, snapshot, &importedFiles, &scannedPaths);
|
|
|
|
findNewFileImports(doc, snapshot, &importedFiles, &scannedPaths);
|
|
|
|
findNewLibraryImports(doc, snapshot, modelManager, &importedFiles, &scannedPaths,
|
|
|
|
&newLibraries);
|
|
|
|
}
|
2014-01-23 14:28:31 +01:00
|
|
|
|
|
|
|
// add new files to parse list
|
2022-06-20 12:35:13 +02:00
|
|
|
for (const Utils::FilePath &file : qAsConst(importedFiles)) {
|
2019-10-23 14:03:13 +02:00
|
|
|
if (!files.contains(file))
|
2014-01-23 14:28:31 +01:00
|
|
|
files.append(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
modelManager->updateDocument(doc);
|
|
|
|
if (emitDocChangedOnDisk)
|
|
|
|
modelManager->emitDocumentChangedOnDisk(doc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class FutureReporter
|
|
|
|
{
|
|
|
|
public:
|
2019-10-23 14:03:13 +02:00
|
|
|
FutureReporter(QFutureInterface<void> &future, int multiplier, int base)
|
|
|
|
: future(future), multiplier(multiplier), base(base)
|
|
|
|
{}
|
|
|
|
|
2014-01-23 14:28:31 +01:00
|
|
|
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,
|
2019-10-23 14:03:13 +02:00
|
|
|
const WorkingCopy &workingCopy,
|
2022-06-20 12:35:13 +02:00
|
|
|
QList<Utils::FilePath> files,
|
2019-10-23 14:03:13 +02:00
|
|
|
ModelManagerInterface *modelManager,
|
|
|
|
Dialect mainLanguage,
|
|
|
|
bool emitDocChangedOnDisk)
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
2019-10-23 14:03:13 +02:00
|
|
|
const int progressMax = 100;
|
|
|
|
FutureReporter reporter(future, progressMax, 0);
|
|
|
|
future.setProgressRange(0, progressMax);
|
2014-01-23 14:28:31 +01:00
|
|
|
|
|
|
|
// paths we have scanned for files and added to the files list
|
2022-06-20 12:35:13 +02:00
|
|
|
QSet<Utils::FilePath> scannedPaths;
|
2014-01-23 14:28:31 +01:00
|
|
|
// libraries we've found while scanning imports
|
2022-06-20 12:35:13 +02:00
|
|
|
QSet<Utils::FilePath> newLibraries;
|
2019-10-23 14:03:13 +02:00
|
|
|
parseLoop(scannedPaths, newLibraries, workingCopy, std::move(files), modelManager, mainLanguage,
|
2014-01-23 14:28:31 +01:00
|
|
|
emitDocChangedOnDisk, reporter);
|
2019-10-23 14:03:13 +02:00
|
|
|
future.setProgressValue(progressMax);
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
struct ScanItem {
|
2022-06-20 12:35:13 +02:00
|
|
|
Utils::FilePath path;
|
2019-10-23 14:03:13 +02:00
|
|
|
int depth = 0;
|
|
|
|
Dialect language = Dialect::AnyLanguage;
|
2014-01-23 14:28:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
void ModelManagerInterface::importScan(QFutureInterface<void> &future,
|
2019-10-23 14:03:13 +02:00
|
|
|
const ModelManagerInterface::WorkingCopy &workingCopy,
|
|
|
|
const PathsAndLanguages &paths,
|
|
|
|
ModelManagerInterface *modelManager,
|
|
|
|
bool emitDocChangedOnDisk, bool libOnly, bool forceRescan)
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
|
|
|
// paths we have scanned for files and added to the files list
|
2022-06-20 12:35:13 +02:00
|
|
|
QSet<Utils::FilePath> scannedPaths;
|
2014-07-29 20:03:48 +02:00
|
|
|
{
|
|
|
|
QMutexLocker l(&modelManager->m_mutex);
|
|
|
|
scannedPaths = modelManager->m_scannedPaths;
|
|
|
|
}
|
2014-01-23 14:28:31 +01:00
|
|
|
// libraries we've found while scanning imports
|
2022-06-20 12:35:13 +02:00
|
|
|
QSet<Utils::FilePath> newLibraries;
|
2014-01-23 14:28:31 +01:00
|
|
|
|
|
|
|
QVector<ScanItem> pathsToScan;
|
|
|
|
pathsToScan.reserve(paths.size());
|
|
|
|
{
|
|
|
|
QMutexLocker l(&modelManager->m_mutex);
|
2019-10-23 14:03:13 +02:00
|
|
|
for (const auto &path : paths) {
|
2022-06-20 12:35:13 +02:00
|
|
|
Utils::FilePath cPath = path.path().cleanPath();
|
2017-03-07 15:49:02 +01:00
|
|
|
if (!forceRescan && modelManager->m_scannedPaths.contains(cPath))
|
2014-01-23 14:28:31 +01:00
|
|
|
continue;
|
2019-10-23 14:03:13 +02:00
|
|
|
pathsToScan.append({cPath, 0, path.language()});
|
2014-01-23 14:28:31 +01:00
|
|
|
modelManager->m_scannedPaths.insert(cPath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const int maxScanDepth = 5;
|
|
|
|
int progressRange = pathsToScan.size() * (1 << (2 + maxScanDepth));
|
2019-10-23 14:03:13 +02:00
|
|
|
int totalWork = progressRange;
|
|
|
|
int workDone = 0;
|
2014-01-23 14:28:31 +01:00
|
|
|
future.setProgressRange(0, progressRange); // update max length while iterating?
|
|
|
|
const Snapshot snapshot = modelManager->snapshot();
|
2014-12-04 13:20:18 +01:00
|
|
|
bool isCanceled = future.isCanceled();
|
|
|
|
while (!pathsToScan.isEmpty() && !isCanceled) {
|
2014-01-23 14:28:31 +01:00
|
|
|
ScanItem toScan = pathsToScan.last();
|
|
|
|
pathsToScan.pop_back();
|
2014-05-26 18:20:00 +02:00
|
|
|
int pathBudget = (1 << (maxScanDepth + 2 - toScan.depth));
|
2017-03-07 15:49:02 +01:00
|
|
|
if (forceRescan || !scannedPaths.contains(toScan.path)) {
|
2022-06-20 12:35:13 +02:00
|
|
|
QList<Utils::FilePath> importedFiles;
|
2017-03-07 15:49:02 +01:00
|
|
|
if (forceRescan ||
|
|
|
|
(!findNewQmlLibraryInPath(toScan.path, snapshot, modelManager, &importedFiles,
|
2019-10-23 14:03:13 +02:00
|
|
|
&scannedPaths, &newLibraries, true)
|
2017-03-07 15:49:02 +01:00
|
|
|
&& !libOnly && snapshot.documentsInDirectory(toScan.path).isEmpty())) {
|
2014-01-23 14:28:31 +01:00
|
|
|
importedFiles += filesInDirectoryForLanguages(toScan.path,
|
2019-10-23 14:03:13 +02:00
|
|
|
toScan.language.companionLanguages());
|
2017-03-07 15:49:02 +01:00
|
|
|
}
|
2014-01-23 14:28:31 +01:00
|
|
|
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,
|
2014-07-22 19:06:44 +02:00
|
|
|
toScan.language, emitDocChangedOnDisk, reporter); // run in parallel??
|
2014-01-23 14:28:31 +01:00
|
|
|
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) {
|
2022-06-20 12:35:13 +02:00
|
|
|
Utils::FilePath dir = toScan.path;
|
|
|
|
const QList<Utils::FilePath> subDirs = dir.dirEntries(QDir::Dirs | QDir::NoDotAndDotDot);
|
2014-01-23 14:28:31 +01:00
|
|
|
workDone += 1;
|
|
|
|
totalWork += pathBudget / 2 * subDirs.size() - pathBudget * 3 / 4 + 1;
|
2022-06-20 12:35:13 +02:00
|
|
|
for (const Utils::FilePath &path : subDirs)
|
|
|
|
pathsToScan.append({path.absoluteFilePath(), toScan.depth + 1, toScan.language});
|
2014-01-23 14:28:31 +01:00
|
|
|
} else {
|
2014-06-02 16:46:39 +02:00
|
|
|
workDone += pathBudget * 3 / 4;
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
future.setProgressValue(progressRange * workDone / totalWork);
|
2014-12-04 13:20:18 +01:00
|
|
|
isCanceled = future.isCanceled();
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
future.setProgressValue(progressRange);
|
2014-12-04 13:20:18 +01:00
|
|
|
if (isCanceled) {
|
2014-01-23 14:28:31 +01:00
|
|
|
// assume no work has been done
|
|
|
|
QMutexLocker l(&modelManager->m_mutex);
|
2019-10-23 14:03:13 +02:00
|
|
|
for (const auto &path : paths)
|
2022-06-20 12:35:13 +02:00
|
|
|
modelManager->m_scannedPaths.remove(path.path());
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
QList<Utils::FilePath> ModelManagerInterface::importPathsNames() const
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
2022-06-20 12:35:13 +02:00
|
|
|
QList<Utils::FilePath> names;
|
2014-01-23 14:28:31 +01:00
|
|
|
QMutexLocker l(&m_mutex);
|
2017-09-28 16:24:13 +02:00
|
|
|
names.reserve(m_allImportPaths.size());
|
|
|
|
for (const PathAndLanguage &x: m_allImportPaths)
|
2022-06-20 12:35:13 +02:00
|
|
|
names << x.path();
|
2017-09-28 16:24:13 +02:00
|
|
|
return names;
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
QmlLanguageBundles ModelManagerInterface::activeBundles() const
|
|
|
|
{
|
|
|
|
QMutexLocker l(&m_mutex);
|
|
|
|
return m_activeBundles;
|
|
|
|
}
|
|
|
|
|
|
|
|
QmlLanguageBundles ModelManagerInterface::extendedBundles() const
|
|
|
|
{
|
|
|
|
QMutexLocker l(&m_mutex);
|
|
|
|
return m_extendedBundles;
|
|
|
|
}
|
|
|
|
|
2014-07-22 19:06:44 +02:00
|
|
|
void ModelManagerInterface::maybeScan(const PathsAndLanguages &importPaths)
|
2014-04-08 11:36:03 +02:00
|
|
|
{
|
2019-01-18 20:28:55 +01:00
|
|
|
if (m_indexerDisabled)
|
2014-09-08 17:59:25 +02:00
|
|
|
return;
|
2014-07-22 19:06:44 +02:00
|
|
|
PathsAndLanguages pathToScan;
|
2014-04-08 11:36:03 +02:00
|
|
|
{
|
|
|
|
QMutexLocker l(&m_mutex);
|
2019-10-23 14:03:13 +02:00
|
|
|
for (const PathAndLanguage &importPath : importPaths)
|
2022-06-20 12:35:13 +02:00
|
|
|
if (!m_scannedPaths.contains(importPath.path()))
|
2014-07-22 19:06:44 +02:00
|
|
|
pathToScan.maybeInsert(importPath);
|
2014-04-08 11:36:03 +02:00
|
|
|
}
|
|
|
|
|
2020-05-19 11:18:45 +02:00
|
|
|
if (pathToScan.length() >= 1) {
|
2016-02-08 16:26:19 +01:00
|
|
|
QFuture<void> result = Utils::runAsync(&ModelManagerInterface::importScan,
|
|
|
|
workingCopyInternal(), pathToScan,
|
2017-03-07 15:49:02 +01:00
|
|
|
this, true, true, false);
|
2020-05-10 16:00:31 +02:00
|
|
|
addFuture(result);
|
2014-05-02 10:00:20 +02:00
|
|
|
addTaskInternal(result, tr("Scanning QML Imports"), Constants::TASK_IMPORT_SCAN);
|
2014-04-08 11:36:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
static QList<Utils::FilePath> minimalPrefixPaths(const QList<Utils::FilePath> &paths)
|
qmljs: avoid linking to files in the build directory
cmake creates a consistent uri structure in the build directory.
We use that as import path, but when we find a type in them we should
refer back to the original file, as editing those is dangerous because
any edit are lost with the next build.
To find the original file we use the qrc, as the qrc path is mostly
the same of as the uri path.
It is possible to add prefixes which would make an exact match fail,
so we compare the paths from the right to the left and find the
longest match.
To acheive this:
* QrcParser keeps a reversedResources, so the match from right can be
done efficiently, and provides a longestReverseMatches method
* the model manager keeps a list of all common prefixes of the
application paths (build directories), and identify all files in
build directories
* the method fileToSource identifies the files in the build directory
and tries to find the corresponding source file, warning if he
cannot find it
* fileToSource is used for follow Symbol and find usages
We could use fileToSource much more aggressively, to use to in editor
content for the files in the build directory, increasing the
consistency, but that is a more dangerous change for later.
Fixes: QTCREATORBUG-27173
Change-Id: Iea61b9825e5f6e433a7390cf2de9564b792458a5
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-06-17 15:46:52 +02:00
|
|
|
{
|
|
|
|
QList<Utils::FilePath> sortedPaths;
|
|
|
|
// find minimal prefix, ensure '/' at end
|
2022-06-20 12:35:13 +02:00
|
|
|
for (Utils::FilePath path : qAsConst(paths)) {
|
qmljs: avoid linking to files in the build directory
cmake creates a consistent uri structure in the build directory.
We use that as import path, but when we find a type in them we should
refer back to the original file, as editing those is dangerous because
any edit are lost with the next build.
To find the original file we use the qrc, as the qrc path is mostly
the same of as the uri path.
It is possible to add prefixes which would make an exact match fail,
so we compare the paths from the right to the left and find the
longest match.
To acheive this:
* QrcParser keeps a reversedResources, so the match from right can be
done efficiently, and provides a longestReverseMatches method
* the model manager keeps a list of all common prefixes of the
application paths (build directories), and identify all files in
build directories
* the method fileToSource identifies the files in the build directory
and tries to find the corresponding source file, warning if he
cannot find it
* fileToSource is used for follow Symbol and find usages
We could use fileToSource much more aggressively, to use to in editor
content for the files in the build directory, increasing the
consistency, but that is a more dangerous change for later.
Fixes: QTCREATORBUG-27173
Change-Id: Iea61b9825e5f6e433a7390cf2de9564b792458a5
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-06-17 15:46:52 +02:00
|
|
|
if (!path.endsWith("/"))
|
|
|
|
path.setPath(path.path() + "/");
|
|
|
|
if (path.path().length() > 1)
|
|
|
|
sortedPaths.append(path);
|
|
|
|
}
|
|
|
|
std::sort(sortedPaths.begin(), sortedPaths.end());
|
|
|
|
QList<Utils::FilePath> res;
|
|
|
|
QString lastPrefix;
|
|
|
|
for (auto it = sortedPaths.begin(); it != sortedPaths.end(); ++it) {
|
|
|
|
if (lastPrefix.isEmpty() || !it->startsWith(lastPrefix)) {
|
|
|
|
lastPrefix = it->path();
|
|
|
|
res.append(*it);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2014-01-23 14:28:31 +01:00
|
|
|
void ModelManagerInterface::updateImportPaths()
|
|
|
|
{
|
2019-01-18 20:28:55 +01:00
|
|
|
if (m_indexerDisabled)
|
2014-09-08 17:59:25 +02:00
|
|
|
return;
|
2014-07-22 19:06:44 +02:00
|
|
|
PathsAndLanguages allImportPaths;
|
2022-06-20 12:35:13 +02:00
|
|
|
QList<Utils::FilePath> allApplicationDirectories;
|
2014-01-23 14:28:31 +01:00
|
|
|
QmlLanguageBundles activeBundles;
|
|
|
|
QmlLanguageBundles extendedBundles;
|
2019-10-23 14:03:13 +02:00
|
|
|
for (const ProjectInfo &pInfo : qAsConst(m_projects)) {
|
|
|
|
for (const auto &importPath : pInfo.importPaths) {
|
|
|
|
const QString canonicalPath = importPath.path().toFileInfo().canonicalFilePath();
|
|
|
|
if (!canonicalPath.isEmpty()) {
|
2019-05-28 13:49:26 +02:00
|
|
|
allImportPaths.maybeInsert(Utils::FilePath::fromString(canonicalPath),
|
2019-10-23 14:03:13 +02:00
|
|
|
importPath.language());
|
|
|
|
}
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
2019-10-22 16:29:40 +02:00
|
|
|
allApplicationDirectories.append(pInfo.applicationDirectories);
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
2019-10-23 14:03:13 +02:00
|
|
|
|
|
|
|
for (const ViewerContext &vContext : qAsConst(m_defaultVContexts)) {
|
2022-06-20 12:35:13 +02:00
|
|
|
for (const Utils::FilePath &path : vContext.paths)
|
|
|
|
allImportPaths.maybeInsert(path, vContext.language);
|
2019-10-22 16:29:40 +02:00
|
|
|
allApplicationDirectories.append(vContext.applicationDirectories);
|
2014-04-11 23:07:52 +02:00
|
|
|
}
|
2019-10-23 14:03:13 +02:00
|
|
|
|
|
|
|
for (const ProjectInfo &pInfo : qAsConst(m_projects)) {
|
|
|
|
activeBundles.mergeLanguageBundles(pInfo.activeBundle);
|
|
|
|
const auto languages = pInfo.activeBundle.languages();
|
|
|
|
for (Dialect l : languages) {
|
|
|
|
const auto paths = pInfo.activeBundle.bundleForLanguage(l).searchPaths().stringList();
|
|
|
|
for (const QString &path : paths) {
|
2014-01-23 14:28:31 +01:00
|
|
|
const QString canonicalPath = QFileInfo(path).canonicalFilePath();
|
|
|
|
if (!canonicalPath.isEmpty())
|
2019-05-28 13:49:26 +02:00
|
|
|
allImportPaths.maybeInsert(Utils::FilePath::fromString(canonicalPath), l);
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-10-23 14:03:13 +02:00
|
|
|
|
|
|
|
for (const ProjectInfo &pInfo : qAsConst(m_projects)) {
|
2021-10-12 17:32:48 +02:00
|
|
|
if (!pInfo.qtQmlPath.isEmpty())
|
|
|
|
allImportPaths.maybeInsert(pInfo.qtQmlPath, Dialect::QmlQtQuick2);
|
2014-04-11 23:07:52 +02:00
|
|
|
}
|
2019-10-23 14:03:13 +02:00
|
|
|
|
2014-04-11 23:07:52 +02:00
|
|
|
{
|
2021-10-12 17:32:48 +02:00
|
|
|
const FilePath pathAtt = defaultProjectInfo().qtQmlPath;
|
2014-07-22 19:06:44 +02:00
|
|
|
if (!pathAtt.isEmpty())
|
2021-10-12 17:32:48 +02:00
|
|
|
allImportPaths.maybeInsert(pathAtt, Dialect::QmlQtQuick2);
|
2014-04-11 23:07:52 +02:00
|
|
|
}
|
2018-02-19 12:26:51 +01:00
|
|
|
|
2021-03-17 11:32:18 +01:00
|
|
|
for (const auto &importPath : defaultProjectInfo().importPaths) {
|
|
|
|
allImportPaths.maybeInsert(importPath);
|
|
|
|
}
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
for (const Utils::FilePath &path : qAsConst(m_defaultImportPaths))
|
|
|
|
allImportPaths.maybeInsert(path, Dialect::Qml);
|
2014-07-22 19:06:44 +02:00
|
|
|
allImportPaths.compact();
|
2019-10-22 16:29:40 +02:00
|
|
|
allApplicationDirectories = Utils::filteredUnique(allApplicationDirectories);
|
2014-01-23 14:28:31 +01:00
|
|
|
|
|
|
|
{
|
|
|
|
QMutexLocker l(&m_mutex);
|
|
|
|
m_allImportPaths = allImportPaths;
|
|
|
|
m_activeBundles = activeBundles;
|
|
|
|
m_extendedBundles = extendedBundles;
|
qmljs: avoid linking to files in the build directory
cmake creates a consistent uri structure in the build directory.
We use that as import path, but when we find a type in them we should
refer back to the original file, as editing those is dangerous because
any edit are lost with the next build.
To find the original file we use the qrc, as the qrc path is mostly
the same of as the uri path.
It is possible to add prefixes which would make an exact match fail,
so we compare the paths from the right to the left and find the
longest match.
To acheive this:
* QrcParser keeps a reversedResources, so the match from right can be
done efficiently, and provides a longestReverseMatches method
* the model manager keeps a list of all common prefixes of the
application paths (build directories), and identify all files in
build directories
* the method fileToSource identifies the files in the build directory
and tries to find the corresponding source file, warning if he
cannot find it
* fileToSource is used for follow Symbol and find usages
We could use fileToSource much more aggressively, to use to in editor
content for the files in the build directory, increasing the
consistency, but that is a more dangerous change for later.
Fixes: QTCREATORBUG-27173
Change-Id: Iea61b9825e5f6e433a7390cf2de9564b792458a5
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-06-17 15:46:52 +02:00
|
|
|
m_applicationPaths = minimalPrefixPaths(allApplicationDirectories);
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// check if any file in the snapshot imports something new in the new paths
|
2014-02-13 11:11:40 +01:00
|
|
|
Snapshot snapshot = m_validSnapshot;
|
2022-06-20 12:35:13 +02:00
|
|
|
QList<Utils::FilePath> importedFiles;
|
|
|
|
QSet<Utils::FilePath> scannedPaths;
|
|
|
|
QSet<Utils::FilePath> newLibraries;
|
2019-10-23 14:03:13 +02:00
|
|
|
for (const Document::Ptr &doc : qAsConst(snapshot))
|
2014-01-23 14:28:31 +01:00
|
|
|
findNewLibraryImports(doc, snapshot, this, &importedFiles, &scannedPaths, &newLibraries);
|
2022-06-20 12:35:13 +02:00
|
|
|
for (const Utils::FilePath &path : qAsConst(allApplicationDirectories)) {
|
qmljs: avoid linking to files in the build directory
cmake creates a consistent uri structure in the build directory.
We use that as import path, but when we find a type in them we should
refer back to the original file, as editing those is dangerous because
any edit are lost with the next build.
To find the original file we use the qrc, as the qrc path is mostly
the same of as the uri path.
It is possible to add prefixes which would make an exact match fail,
so we compare the paths from the right to the left and find the
longest match.
To acheive this:
* QrcParser keeps a reversedResources, so the match from right can be
done efficiently, and provides a longestReverseMatches method
* the model manager keeps a list of all common prefixes of the
application paths (build directories), and identify all files in
build directories
* the method fileToSource identifies the files in the build directory
and tries to find the corresponding source file, warning if he
cannot find it
* fileToSource is used for follow Symbol and find usages
We could use fileToSource much more aggressively, to use to in editor
content for the files in the build directory, increasing the
consistency, but that is a more dangerous change for later.
Fixes: QTCREATORBUG-27173
Change-Id: Iea61b9825e5f6e433a7390cf2de9564b792458a5
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-06-17 15:46:52 +02:00
|
|
|
allImportPaths.maybeInsert(path, Dialect::Qml);
|
|
|
|
findNewQmlApplicationInPath(path, snapshot, this, &newLibraries);
|
2021-12-16 14:52:26 +01:00
|
|
|
}
|
qmljs: avoid linking to files in the build directory
cmake creates a consistent uri structure in the build directory.
We use that as import path, but when we find a type in them we should
refer back to the original file, as editing those is dangerous because
any edit are lost with the next build.
To find the original file we use the qrc, as the qrc path is mostly
the same of as the uri path.
It is possible to add prefixes which would make an exact match fail,
so we compare the paths from the right to the left and find the
longest match.
To acheive this:
* QrcParser keeps a reversedResources, so the match from right can be
done efficiently, and provides a longestReverseMatches method
* the model manager keeps a list of all common prefixes of the
application paths (build directories), and identify all files in
build directories
* the method fileToSource identifies the files in the build directory
and tries to find the corresponding source file, warning if he
cannot find it
* fileToSource is used for follow Symbol and find usages
We could use fileToSource much more aggressively, to use to in editor
content for the files in the build directory, increasing the
consistency, but that is a more dangerous change for later.
Fixes: QTCREATORBUG-27173
Change-Id: Iea61b9825e5f6e433a7390cf2de9564b792458a5
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-06-17 15:46:52 +02:00
|
|
|
for (const Utils::FilePath &qrcPath : generatedQrc(allApplicationDirectories))
|
2022-06-20 12:35:13 +02:00
|
|
|
updateQrcFile(qrcPath);
|
2014-01-23 14:28:31 +01:00
|
|
|
|
|
|
|
updateSourceFiles(importedFiles, true);
|
|
|
|
|
|
|
|
if (!m_shouldScanImports)
|
|
|
|
return;
|
2014-07-22 19:06:44 +02:00
|
|
|
maybeScan(allImportPaths);
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
void ModelManagerInterface::loadPluginTypes(const Utils::FilePath &libraryPath,
|
|
|
|
const Utils::FilePath &importPath,
|
|
|
|
const QString &importUri,
|
|
|
|
const QString &importVersion)
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
|
|
|
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();
|
|
|
|
|
2021-06-21 13:51:55 +02:00
|
|
|
QMutexLocker locker(&g_instanceMutex);
|
|
|
|
if (g_instance) // delegate actual queuing to the gui thread
|
|
|
|
QMetaObject::invokeMethod(g_instance, [=] { queueCppQmlTypeUpdate(doc, scan); });
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
2017-04-26 13:18:26 +02:00
|
|
|
m_queuedCppDocuments.insert(doc->fileName(), {doc, scan});
|
2014-01-23 14:28:31 +01:00
|
|
|
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;
|
|
|
|
|
2016-02-08 16:26:19 +01:00
|
|
|
m_cppQmlTypesUpdater = Utils::runAsync(&ModelManagerInterface::updateCppQmlTypes,
|
2014-01-23 14:28:31 +01:00
|
|
|
this, cppModelManager->snapshot(), m_queuedCppDocuments);
|
|
|
|
m_queuedCppDocuments.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ModelManagerInterface::asyncReset()
|
|
|
|
{
|
|
|
|
m_asyncResetTimer->start();
|
|
|
|
}
|
|
|
|
|
2016-07-08 13:09:56 +02:00
|
|
|
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()) {
|
2021-10-14 22:32:09 +03:00
|
|
|
hasNewInfo = hasNewInfo || newData.remove(fileName);
|
2016-07-08 13:09:56 +02:00
|
|
|
} else {
|
|
|
|
ModelManagerInterface::CppData &data = newData[fileName];
|
|
|
|
if (!hasNewInfo && (data.exportedTypes.size() != exported.size()
|
2019-10-23 14:03:13 +02:00
|
|
|
|| data.contextProperties != contextProperties)) {
|
2016-07-08 13:09:56 +02:00
|
|
|
hasNewInfo = true;
|
2019-10-23 14:03:13 +02:00
|
|
|
}
|
2016-07-08 13:09:56 +02:00
|
|
|
if (!hasNewInfo) {
|
|
|
|
QHash<QString, QByteArray> newFingerprints;
|
2019-10-23 14:03:13 +02:00
|
|
|
for (const auto &newType : qAsConst(exported))
|
2016-07-08 13:09:56 +02:00
|
|
|
newFingerprints[newType->className()]=newType->fingerprint();
|
2019-10-23 14:03:13 +02:00
|
|
|
for (const auto &oldType : qAsConst(data.exportedTypes)) {
|
2016-07-08 13:09:56 +02:00
|
|
|
if (newFingerprints.value(oldType->className()) != oldType->fingerprint()) {
|
|
|
|
hasNewInfo = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
data.exportedTypes = exported;
|
|
|
|
data.contextProperties = contextProperties;
|
|
|
|
}
|
|
|
|
return hasNewInfo;
|
|
|
|
}
|
|
|
|
|
2019-10-23 14:03:13 +02:00
|
|
|
void ModelManagerInterface::updateCppQmlTypes(
|
|
|
|
QFutureInterface<void> &futureInterface, ModelManagerInterface *qmlModelManager,
|
|
|
|
const CPlusPlus::Snapshot &snapshot,
|
|
|
|
const QHash<QString, QPair<CPlusPlus::Document::Ptr, bool>> &documents)
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
2017-08-17 11:31:31 +02:00
|
|
|
futureInterface.setProgressRange(0, documents.size());
|
|
|
|
futureInterface.setProgressValue(0);
|
2016-07-08 13:09:56 +02:00
|
|
|
|
|
|
|
CppDataHash newData;
|
2019-10-23 14:03:13 +02:00
|
|
|
QHash<QString, QList<CPlusPlus::Document::Ptr>> newDeclarations;
|
2016-07-08 13:09:56 +02:00
|
|
|
{
|
|
|
|
QMutexLocker locker(&qmlModelManager->m_cppDataMutex);
|
|
|
|
newData = qmlModelManager->m_cppDataHash;
|
|
|
|
newDeclarations = qmlModelManager->m_cppDeclarationFiles;
|
|
|
|
}
|
2014-01-23 14:28:31 +01:00
|
|
|
|
|
|
|
FindExportedCppTypes finder(snapshot);
|
|
|
|
|
|
|
|
bool hasNewInfo = false;
|
2019-10-23 14:03:13 +02:00
|
|
|
using DocScanPair = QPair<CPlusPlus::Document::Ptr, bool>;
|
|
|
|
for (const DocScanPair &pair : documents) {
|
2017-08-17 11:31:31 +02:00
|
|
|
if (futureInterface.isCanceled())
|
2014-01-23 14:28:31 +01:00
|
|
|
return;
|
2017-08-17 11:31:31 +02:00
|
|
|
futureInterface.setProgressValue(futureInterface.progressValue() + 1);
|
2014-01-23 14:28:31 +01:00
|
|
|
|
|
|
|
CPlusPlus::Document::Ptr doc = pair.first;
|
|
|
|
const bool scan = pair.second;
|
|
|
|
const QString fileName = doc->fileName();
|
|
|
|
if (!scan) {
|
2021-10-14 22:32:09 +03:00
|
|
|
hasNewInfo = newData.remove(fileName) || hasNewInfo;
|
2019-10-23 14:03:13 +02:00
|
|
|
const auto savedDocs = newDeclarations.value(fileName);
|
|
|
|
for (const CPlusPlus::Document::Ptr &savedDoc : savedDocs) {
|
2016-10-28 12:53:24 +02:00
|
|
|
finder(savedDoc);
|
|
|
|
hasNewInfo = rescanExports(savedDoc->fileName(), finder, newData) || hasNewInfo;
|
2016-07-08 13:09:56 +02:00
|
|
|
}
|
2014-01-23 14:28:31 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-07-08 13:09:56 +02:00
|
|
|
for (auto it = newDeclarations.begin(), end = newDeclarations.end(); it != end;) {
|
2016-10-28 12:53:24 +02:00
|
|
|
for (auto docIt = it->begin(), endDocIt = it->end(); docIt != endDocIt;) {
|
2019-10-23 14:03:13 +02:00
|
|
|
const CPlusPlus::Document::Ptr &savedDoc = *docIt;
|
2016-10-28 12:53:24 +02:00
|
|
|
if (savedDoc->fileName() == fileName) {
|
|
|
|
savedDoc->releaseSourceAndAST();
|
|
|
|
it->erase(docIt);
|
|
|
|
break;
|
2014-07-14 18:39:18 +02:00
|
|
|
}
|
2019-10-23 14:03:13 +02:00
|
|
|
++docIt;
|
2014-07-14 18:39:18 +02:00
|
|
|
}
|
2016-10-28 12:53:24 +02:00
|
|
|
if (it->isEmpty())
|
|
|
|
it = newDeclarations.erase(it);
|
|
|
|
else
|
|
|
|
++it;
|
2016-07-08 13:09:56 +02:00
|
|
|
}
|
|
|
|
|
2019-10-23 14:03:13 +02:00
|
|
|
const auto found = finder(doc);
|
|
|
|
for (const QString &declarationFile : found) {
|
2016-10-28 12:53:24 +02:00
|
|
|
newDeclarations[declarationFile].append(doc);
|
2016-07-08 13:09:56 +02:00
|
|
|
doc->keepSourceAndAST(); // keep for later reparsing when dependent doc changes
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
2016-10-28 12:53:24 +02:00
|
|
|
hasNewInfo = rescanExports(fileName, finder, newData) || hasNewInfo;
|
2014-01-23 14:28:31 +01:00
|
|
|
doc->releaseSourceAndAST();
|
|
|
|
}
|
|
|
|
|
|
|
|
QMutexLocker locker(&qmlModelManager->m_cppDataMutex);
|
|
|
|
qmlModelManager->m_cppDataHash = newData;
|
2016-07-08 13:09:56 +02:00
|
|
|
qmlModelManager->m_cppDeclarationFiles = newDeclarations;
|
2014-01-23 14:28:31 +01:00
|
|
|
if (hasNewInfo)
|
|
|
|
// one could get away with re-linking the cpp types...
|
2020-11-11 16:34:39 +01:00
|
|
|
QMetaObject::invokeMethod(qmlModelManager, &ModelManagerInterface::asyncReset);
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ModelManagerInterface::CppDataHash ModelManagerInterface::cppData() const
|
|
|
|
{
|
|
|
|
QMutexLocker locker(&m_cppDataMutex);
|
|
|
|
return m_cppDataHash;
|
|
|
|
}
|
|
|
|
|
|
|
|
LibraryInfo ModelManagerInterface::builtins(const Document::Ptr &doc) const
|
|
|
|
{
|
2019-01-17 14:06:53 +01:00
|
|
|
const ProjectInfo info = projectInfoForPath(doc->fileName());
|
2020-04-21 13:46:27 +02:00
|
|
|
if (!info.qtQmlPath.isEmpty())
|
2021-10-12 18:00:17 +02:00
|
|
|
return m_validSnapshot.libraryInfo(info.qtQmlPath);
|
2019-01-17 14:06:53 +01:00
|
|
|
return LibraryInfo();
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ViewerContext ModelManagerInterface::completeVContext(const ViewerContext &vCtx,
|
2014-04-11 23:07:52 +02:00
|
|
|
const Document::Ptr &doc) const
|
2020-05-20 12:47:09 +03:00
|
|
|
{
|
|
|
|
return getVContext(vCtx, doc, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
ViewerContext ModelManagerInterface::getVContext(const ViewerContext &vCtx,
|
|
|
|
const Document::Ptr &doc,
|
|
|
|
bool limitToProject) const
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
|
|
|
ViewerContext res = vCtx;
|
2014-04-11 23:07:52 +02:00
|
|
|
|
2014-07-01 19:58:23 +02:00
|
|
|
if (!doc.isNull()
|
2014-07-22 19:06:44 +02:00
|
|
|
&& ((vCtx.language == Dialect::AnyLanguage && doc->language() != Dialect::NoLanguage)
|
|
|
|
|| (vCtx.language == Dialect::Qml
|
2018-02-19 12:26:51 +01:00
|
|
|
&& (doc->language() == Dialect::QmlQtQuick2
|
2014-10-13 16:46:30 +02:00
|
|
|
|| doc->language() == Dialect::QmlQtQuick2Ui))))
|
2014-04-11 23:07:52 +02:00
|
|
|
res.language = doc->language();
|
|
|
|
ProjectInfo info;
|
|
|
|
if (!doc.isNull())
|
2014-12-02 13:24:25 +01:00
|
|
|
info = projectInfoForPath(doc->fileName());
|
2019-01-16 14:28:59 +01:00
|
|
|
ViewerContext defaultVCtx = defaultVContext(res.language, Document::Ptr(nullptr), false);
|
2014-04-11 23:07:52 +02:00
|
|
|
ProjectInfo defaultInfo = defaultProjectInfo();
|
2020-09-29 09:55:41 +02:00
|
|
|
if (info.qtQmlPath.isEmpty()) {
|
2014-04-11 23:07:52 +02:00
|
|
|
info.qtQmlPath = defaultInfo.qtQmlPath;
|
2020-09-29 09:55:41 +02:00
|
|
|
info.qtVersionString = defaultInfo.qtVersionString;
|
|
|
|
}
|
2021-03-17 11:32:18 +01:00
|
|
|
if (info.qtQmlPath.isEmpty() && info.importPaths.size() == 0)
|
|
|
|
info.importPaths = defaultInfo.importPaths;
|
2019-10-22 16:29:40 +02:00
|
|
|
info.applicationDirectories = Utils::filteredUnique(info.applicationDirectories
|
|
|
|
+ defaultInfo.applicationDirectories);
|
2014-01-23 14:28:31 +01:00
|
|
|
switch (res.flags) {
|
|
|
|
case ViewerContext::Complete:
|
|
|
|
break;
|
2014-04-11 23:07:52 +02:00
|
|
|
case ViewerContext::AddAllPathsAndDefaultSelectors:
|
|
|
|
res.selectors.append(defaultVCtx.selectors);
|
2018-01-10 16:51:58 +01:00
|
|
|
Q_FALLTHROUGH();
|
2014-01-23 14:28:31 +01:00
|
|
|
case ViewerContext::AddAllPaths:
|
2014-04-11 23:07:52 +02:00
|
|
|
{
|
2022-06-20 12:35:13 +02:00
|
|
|
for (const Utils::FilePath &path : qAsConst(defaultVCtx.paths))
|
2019-10-23 09:14:58 +02:00
|
|
|
maybeAddPath(res, path);
|
2014-07-22 19:06:44 +02:00
|
|
|
switch (res.language.dialect()) {
|
|
|
|
case Dialect::AnyLanguage:
|
|
|
|
case Dialect::Qml:
|
2022-06-20 12:35:13 +02:00
|
|
|
maybeAddPath(res, info.qtQmlPath);
|
2018-01-10 16:51:58 +01:00
|
|
|
Q_FALLTHROUGH();
|
2014-07-22 19:06:44 +02:00
|
|
|
case Dialect::QmlQtQuick2:
|
2014-10-13 16:46:30 +02:00
|
|
|
case Dialect::QmlQtQuick2Ui:
|
2014-04-11 23:07:52 +02:00
|
|
|
{
|
2014-10-13 16:46:30 +02:00
|
|
|
if (res.language == Dialect::QmlQtQuick2 || res.language == Dialect::QmlQtQuick2Ui)
|
2022-06-20 12:35:13 +02:00
|
|
|
maybeAddPath(res, info.qtQmlPath);
|
2020-05-20 12:47:09 +03:00
|
|
|
|
2014-07-22 19:06:44 +02:00
|
|
|
QList<Dialect> languages = res.language.companionLanguages();
|
2020-05-20 12:47:09 +03:00
|
|
|
auto addPathsOnLanguageMatch = [&](const PathsAndLanguages &importPaths) {
|
|
|
|
for (const auto &importPath : importPaths) {
|
2019-10-23 14:03:13 +02:00
|
|
|
if (languages.contains(importPath.language())
|
|
|
|
|| importPath.language().companionLanguages().contains(res.language)) {
|
2022-06-20 12:35:13 +02:00
|
|
|
maybeAddPath(res, importPath.path());
|
2019-10-23 14:03:13 +02:00
|
|
|
}
|
2014-07-22 19:06:44 +02:00
|
|
|
}
|
2020-05-20 12:47:09 +03:00
|
|
|
};
|
|
|
|
if (limitToProject) {
|
|
|
|
addPathsOnLanguageMatch(info.importPaths);
|
|
|
|
} else {
|
|
|
|
QList<ProjectInfo> allProjects;
|
|
|
|
{
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
allProjects = m_projects.values();
|
|
|
|
}
|
|
|
|
std::sort(allProjects.begin(), allProjects.end(), &pInfoLessThanImports);
|
|
|
|
for (const ProjectInfo &pInfo : qAsConst(allProjects))
|
|
|
|
addPathsOnLanguageMatch(pInfo.importPaths);
|
2014-04-11 23:07:52 +02:00
|
|
|
}
|
2019-10-23 14:03:13 +02:00
|
|
|
const auto environmentPaths = environmentImportPaths();
|
2022-06-20 12:35:13 +02:00
|
|
|
for (const Utils::FilePath &path : environmentPaths)
|
2019-10-23 09:14:58 +02:00
|
|
|
maybeAddPath(res, path);
|
2014-04-11 23:07:52 +02:00
|
|
|
break;
|
|
|
|
}
|
2014-07-22 19:06:44 +02:00
|
|
|
case Dialect::NoLanguage:
|
|
|
|
case Dialect::JavaScript:
|
|
|
|
case Dialect::QmlTypeInfo:
|
|
|
|
case Dialect::Json:
|
|
|
|
case Dialect::QmlQbs:
|
|
|
|
case Dialect::QmlProject:
|
2014-04-11 23:07:52 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case ViewerContext::AddDefaultPathsAndSelectors:
|
|
|
|
res.selectors.append(defaultVCtx.selectors);
|
2018-01-10 16:51:58 +01:00
|
|
|
Q_FALLTHROUGH();
|
2014-04-11 23:07:52 +02:00
|
|
|
case ViewerContext::AddDefaultPaths:
|
2022-06-20 12:35:13 +02:00
|
|
|
for (const Utils::FilePath &path : qAsConst(defaultVCtx.paths))
|
2019-10-23 09:14:58 +02:00
|
|
|
maybeAddPath(res, path);
|
2018-02-19 12:26:51 +01:00
|
|
|
if (res.language == Dialect::AnyLanguage || res.language == Dialect::Qml)
|
2022-06-20 12:35:13 +02:00
|
|
|
maybeAddPath(res, info.qtQmlPath);
|
2014-09-16 15:17:21 +02:00
|
|
|
if (res.language == Dialect::AnyLanguage || res.language == Dialect::Qml
|
2018-02-19 12:26:51 +01:00
|
|
|
|| res.language == Dialect::QmlQtQuick2 || res.language == Dialect::QmlQtQuick2Ui) {
|
2019-10-23 14:03:13 +02:00
|
|
|
const auto environemntPaths = environmentImportPaths();
|
2022-06-20 12:35:13 +02:00
|
|
|
for (const Utils::FilePath &path : environemntPaths)
|
2019-10-23 09:14:58 +02:00
|
|
|
maybeAddPath(res, path);
|
2014-09-04 11:48:17 +02:00
|
|
|
}
|
2014-04-11 23:07:52 +02:00
|
|
|
break;
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
res.flags = ViewerContext::Complete;
|
2019-10-22 16:29:40 +02:00
|
|
|
res.applicationDirectories = info.applicationDirectories;
|
2014-01-23 14:28:31 +01:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2014-07-22 19:06:44 +02:00
|
|
|
ViewerContext ModelManagerInterface::defaultVContext(Dialect language,
|
2014-04-11 23:07:52 +02:00
|
|
|
const Document::Ptr &doc,
|
|
|
|
bool autoComplete) const
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
2014-04-11 23:07:52 +02:00
|
|
|
if (!doc.isNull()) {
|
2014-07-22 19:06:44 +02:00
|
|
|
if (language == Dialect::AnyLanguage && doc->language() != Dialect::NoLanguage)
|
2014-04-11 23:07:52 +02:00
|
|
|
language = doc->language();
|
2014-07-22 19:06:44 +02:00
|
|
|
else if (language == Dialect::Qml &&
|
2018-02-19 12:26:51 +01:00
|
|
|
(doc->language() == Dialect::QmlQtQuick2
|
2014-10-13 16:46:30 +02:00
|
|
|
|| doc->language() == Dialect::QmlQtQuick2Ui))
|
2014-04-11 23:07:52 +02:00
|
|
|
language = doc->language();
|
|
|
|
}
|
|
|
|
ViewerContext defaultCtx;
|
|
|
|
{
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
defaultCtx = m_defaultVContexts.value(language);
|
|
|
|
}
|
2014-07-01 19:58:23 +02:00
|
|
|
defaultCtx.language = language;
|
2019-10-23 14:03:13 +02:00
|
|
|
return autoComplete ? completeVContext(defaultCtx, doc) : defaultCtx;
|
2014-04-11 23:07:52 +02:00
|
|
|
}
|
|
|
|
|
2020-05-20 12:47:09 +03:00
|
|
|
ViewerContext ModelManagerInterface::projectVContext(Dialect language, const Document::Ptr &doc) const
|
|
|
|
{
|
|
|
|
// Returns context limited to the project the file belongs to
|
|
|
|
ViewerContext defaultCtx = defaultVContext(language, doc, false);
|
|
|
|
return getVContext(defaultCtx, doc, true);
|
|
|
|
}
|
|
|
|
|
2014-04-11 23:07:52 +02:00
|
|
|
ModelManagerInterface::ProjectInfo ModelManagerInterface::defaultProjectInfo() const
|
|
|
|
{
|
2021-02-18 20:09:26 +01:00
|
|
|
QMutexLocker locker(&m_mutex);
|
2014-06-30 11:56:28 +02:00
|
|
|
return m_defaultProjectInfo;
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
2014-07-24 11:48:30 +02:00
|
|
|
ModelManagerInterface::ProjectInfo ModelManagerInterface::defaultProjectInfoForProject(
|
|
|
|
ProjectExplorer::Project *) const
|
|
|
|
{
|
|
|
|
return ModelManagerInterface::ProjectInfo();
|
|
|
|
}
|
|
|
|
|
2014-01-23 14:28:31 +01:00
|
|
|
void ModelManagerInterface::setDefaultVContext(const ViewerContext &vContext)
|
|
|
|
{
|
2014-04-11 23:07:52 +02:00
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
m_defaultVContexts[vContext.language] = vContext;
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
2021-03-05 15:14:51 +01:00
|
|
|
void ModelManagerInterface::joinAllThreads(bool cancelOnWait)
|
2014-01-23 14:28:31 +01:00
|
|
|
{
|
2020-05-10 16:00:31 +02:00
|
|
|
while (true) {
|
Refactor tst_joinAllThreads, to be used in ModelManager d'tor
The idea in this approach is that we only collect those futures,
which have resulted from runAsync. The assumption is that
all tasks associated with those futures may sooner or
later finish, without the need to call qApp->processEvents().
OTOH, we don't collect fake futures coming from Utils::onFinished,
as these requires the spinning event loop in order to deliver
the onFinished signal.
So, the new joinAllThreads() method waits for all collected
futures to finish. We also _do_ want canceled and not finished
futures to finish, since even when they are canceled,
they may still be running and using the internals
of possibly destructed ModelManager. This means, we are only
waiting for other threads to be finished, without reporting
their results to e.g. onFinished() handlers.
Some tests require that all onFinished handlers are also processed.
In order to achieve this, we create a loop inside
tst_joinAllThreads() method and we call joinAllThreads(), so
it will wait for all pending queue to finish, and then we call process
events, in order to let finished futures propagate their results
to their respective onFinished() handlers.
Some handlers may have stared another threads when being processed,
so we may expect that some new futures will appear.
So, after processing the events we check if any new events
appeared, and in this case we repeat the loop.
Otherwise, we finish synchronization.
Amends: 96c860159b862460e21be16a6e2839c0b591e016
Task-number: QTCREATORBUG-25350
Change-Id: I5e44150c55f6be00445a5695938482d948990c94
Reviewed-by: Fawzi Mohamed <fawzi.mohamed@qt.io>
2021-03-05 13:54:50 +01:00
|
|
|
FutureSynchronizer futureSynchronizer;
|
2020-05-10 16:00:31 +02:00
|
|
|
{
|
Refactor tst_joinAllThreads, to be used in ModelManager d'tor
The idea in this approach is that we only collect those futures,
which have resulted from runAsync. The assumption is that
all tasks associated with those futures may sooner or
later finish, without the need to call qApp->processEvents().
OTOH, we don't collect fake futures coming from Utils::onFinished,
as these requires the spinning event loop in order to deliver
the onFinished signal.
So, the new joinAllThreads() method waits for all collected
futures to finish. We also _do_ want canceled and not finished
futures to finish, since even when they are canceled,
they may still be running and using the internals
of possibly destructed ModelManager. This means, we are only
waiting for other threads to be finished, without reporting
their results to e.g. onFinished() handlers.
Some tests require that all onFinished handlers are also processed.
In order to achieve this, we create a loop inside
tst_joinAllThreads() method and we call joinAllThreads(), so
it will wait for all pending queue to finish, and then we call process
events, in order to let finished futures propagate their results
to their respective onFinished() handlers.
Some handlers may have stared another threads when being processed,
so we may expect that some new futures will appear.
So, after processing the events we check if any new events
appeared, and in this case we repeat the loop.
Otherwise, we finish synchronization.
Amends: 96c860159b862460e21be16a6e2839c0b591e016
Task-number: QTCREATORBUG-25350
Change-Id: I5e44150c55f6be00445a5695938482d948990c94
Reviewed-by: Fawzi Mohamed <fawzi.mohamed@qt.io>
2021-03-05 13:54:50 +01:00
|
|
|
QMutexLocker locker(&m_futuresMutex);
|
|
|
|
futureSynchronizer = m_futureSynchronizer;
|
|
|
|
m_futureSynchronizer.clearFutures();
|
2020-05-10 16:00:31 +02:00
|
|
|
}
|
2021-03-05 15:14:51 +01:00
|
|
|
futureSynchronizer.setCancelOnWait(cancelOnWait);
|
Refactor tst_joinAllThreads, to be used in ModelManager d'tor
The idea in this approach is that we only collect those futures,
which have resulted from runAsync. The assumption is that
all tasks associated with those futures may sooner or
later finish, without the need to call qApp->processEvents().
OTOH, we don't collect fake futures coming from Utils::onFinished,
as these requires the spinning event loop in order to deliver
the onFinished signal.
So, the new joinAllThreads() method waits for all collected
futures to finish. We also _do_ want canceled and not finished
futures to finish, since even when they are canceled,
they may still be running and using the internals
of possibly destructed ModelManager. This means, we are only
waiting for other threads to be finished, without reporting
their results to e.g. onFinished() handlers.
Some tests require that all onFinished handlers are also processed.
In order to achieve this, we create a loop inside
tst_joinAllThreads() method and we call joinAllThreads(), so
it will wait for all pending queue to finish, and then we call process
events, in order to let finished futures propagate their results
to their respective onFinished() handlers.
Some handlers may have stared another threads when being processed,
so we may expect that some new futures will appear.
So, after processing the events we check if any new events
appeared, and in this case we repeat the loop.
Otherwise, we finish synchronization.
Amends: 96c860159b862460e21be16a6e2839c0b591e016
Task-number: QTCREATORBUG-25350
Change-Id: I5e44150c55f6be00445a5695938482d948990c94
Reviewed-by: Fawzi Mohamed <fawzi.mohamed@qt.io>
2021-03-05 13:54:50 +01:00
|
|
|
if (futureSynchronizer.isEmpty())
|
|
|
|
return;
|
2020-05-10 16:00:31 +02:00
|
|
|
}
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
Refactor tst_joinAllThreads, to be used in ModelManager d'tor
The idea in this approach is that we only collect those futures,
which have resulted from runAsync. The assumption is that
all tasks associated with those futures may sooner or
later finish, without the need to call qApp->processEvents().
OTOH, we don't collect fake futures coming from Utils::onFinished,
as these requires the spinning event loop in order to deliver
the onFinished signal.
So, the new joinAllThreads() method waits for all collected
futures to finish. We also _do_ want canceled and not finished
futures to finish, since even when they are canceled,
they may still be running and using the internals
of possibly destructed ModelManager. This means, we are only
waiting for other threads to be finished, without reporting
their results to e.g. onFinished() handlers.
Some tests require that all onFinished handlers are also processed.
In order to achieve this, we create a loop inside
tst_joinAllThreads() method and we call joinAllThreads(), so
it will wait for all pending queue to finish, and then we call process
events, in order to let finished futures propagate their results
to their respective onFinished() handlers.
Some handlers may have stared another threads when being processed,
so we may expect that some new futures will appear.
So, after processing the events we check if any new events
appeared, and in this case we repeat the loop.
Otherwise, we finish synchronization.
Amends: 96c860159b862460e21be16a6e2839c0b591e016
Task-number: QTCREATORBUG-25350
Change-Id: I5e44150c55f6be00445a5695938482d948990c94
Reviewed-by: Fawzi Mohamed <fawzi.mohamed@qt.io>
2021-03-05 13:54:50 +01:00
|
|
|
void ModelManagerInterface::test_joinAllThreads()
|
2020-05-10 16:00:31 +02:00
|
|
|
{
|
Refactor tst_joinAllThreads, to be used in ModelManager d'tor
The idea in this approach is that we only collect those futures,
which have resulted from runAsync. The assumption is that
all tasks associated with those futures may sooner or
later finish, without the need to call qApp->processEvents().
OTOH, we don't collect fake futures coming from Utils::onFinished,
as these requires the spinning event loop in order to deliver
the onFinished signal.
So, the new joinAllThreads() method waits for all collected
futures to finish. We also _do_ want canceled and not finished
futures to finish, since even when they are canceled,
they may still be running and using the internals
of possibly destructed ModelManager. This means, we are only
waiting for other threads to be finished, without reporting
their results to e.g. onFinished() handlers.
Some tests require that all onFinished handlers are also processed.
In order to achieve this, we create a loop inside
tst_joinAllThreads() method and we call joinAllThreads(), so
it will wait for all pending queue to finish, and then we call process
events, in order to let finished futures propagate their results
to their respective onFinished() handlers.
Some handlers may have stared another threads when being processed,
so we may expect that some new futures will appear.
So, after processing the events we check if any new events
appeared, and in this case we repeat the loop.
Otherwise, we finish synchronization.
Amends: 96c860159b862460e21be16a6e2839c0b591e016
Task-number: QTCREATORBUG-25350
Change-Id: I5e44150c55f6be00445a5695938482d948990c94
Reviewed-by: Fawzi Mohamed <fawzi.mohamed@qt.io>
2021-03-05 13:54:50 +01:00
|
|
|
while (true) {
|
|
|
|
joinAllThreads();
|
|
|
|
// In order to process all onFinished handlers of finished futures
|
|
|
|
QCoreApplication::processEvents();
|
2020-05-10 16:00:31 +02:00
|
|
|
QMutexLocker lock(&m_futuresMutex);
|
Refactor tst_joinAllThreads, to be used in ModelManager d'tor
The idea in this approach is that we only collect those futures,
which have resulted from runAsync. The assumption is that
all tasks associated with those futures may sooner or
later finish, without the need to call qApp->processEvents().
OTOH, we don't collect fake futures coming from Utils::onFinished,
as these requires the spinning event loop in order to deliver
the onFinished signal.
So, the new joinAllThreads() method waits for all collected
futures to finish. We also _do_ want canceled and not finished
futures to finish, since even when they are canceled,
they may still be running and using the internals
of possibly destructed ModelManager. This means, we are only
waiting for other threads to be finished, without reporting
their results to e.g. onFinished() handlers.
Some tests require that all onFinished handlers are also processed.
In order to achieve this, we create a loop inside
tst_joinAllThreads() method and we call joinAllThreads(), so
it will wait for all pending queue to finish, and then we call process
events, in order to let finished futures propagate their results
to their respective onFinished() handlers.
Some handlers may have stared another threads when being processed,
so we may expect that some new futures will appear.
So, after processing the events we check if any new events
appeared, and in this case we repeat the loop.
Otherwise, we finish synchronization.
Amends: 96c860159b862460e21be16a6e2839c0b591e016
Task-number: QTCREATORBUG-25350
Change-Id: I5e44150c55f6be00445a5695938482d948990c94
Reviewed-by: Fawzi Mohamed <fawzi.mohamed@qt.io>
2021-03-05 13:54:50 +01:00
|
|
|
// If handlers created new futures, repeat the loop
|
|
|
|
if (m_futureSynchronizer.isEmpty())
|
|
|
|
return;
|
2020-05-10 16:00:31 +02:00
|
|
|
}
|
Refactor tst_joinAllThreads, to be used in ModelManager d'tor
The idea in this approach is that we only collect those futures,
which have resulted from runAsync. The assumption is that
all tasks associated with those futures may sooner or
later finish, without the need to call qApp->processEvents().
OTOH, we don't collect fake futures coming from Utils::onFinished,
as these requires the spinning event loop in order to deliver
the onFinished signal.
So, the new joinAllThreads() method waits for all collected
futures to finish. We also _do_ want canceled and not finished
futures to finish, since even when they are canceled,
they may still be running and using the internals
of possibly destructed ModelManager. This means, we are only
waiting for other threads to be finished, without reporting
their results to e.g. onFinished() handlers.
Some tests require that all onFinished handlers are also processed.
In order to achieve this, we create a loop inside
tst_joinAllThreads() method and we call joinAllThreads(), so
it will wait for all pending queue to finish, and then we call process
events, in order to let finished futures propagate their results
to their respective onFinished() handlers.
Some handlers may have stared another threads when being processed,
so we may expect that some new futures will appear.
So, after processing the events we check if any new events
appeared, and in this case we repeat the loop.
Otherwise, we finish synchronization.
Amends: 96c860159b862460e21be16a6e2839c0b591e016
Task-number: QTCREATORBUG-25350
Change-Id: I5e44150c55f6be00445a5695938482d948990c94
Reviewed-by: Fawzi Mohamed <fawzi.mohamed@qt.io>
2021-03-05 13:54:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void ModelManagerInterface::addFuture(const QFuture<void> &future)
|
|
|
|
{
|
|
|
|
QMutexLocker lock(&m_futuresMutex);
|
|
|
|
m_futureSynchronizer.addFuture(future);
|
2020-05-10 16:00:31 +02:00
|
|
|
}
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
Document::Ptr ModelManagerInterface::ensuredGetDocumentForPath(const Utils::FilePath &filePath)
|
2014-06-26 13:52:36 +02:00
|
|
|
{
|
|
|
|
QmlJS::Document::Ptr document = newestSnapshot().document(filePath);
|
|
|
|
if (!document) {
|
2014-07-22 19:06:44 +02:00
|
|
|
document = QmlJS::Document::create(filePath, QmlJS::Dialect::Qml);
|
2014-06-26 13:52:36 +02:00
|
|
|
QMutexLocker lock(&m_mutex);
|
|
|
|
|
|
|
|
m_newestSnapshot.insert(document);
|
|
|
|
}
|
|
|
|
|
|
|
|
return document;
|
|
|
|
}
|
|
|
|
|
2014-01-23 14:28:31 +01:00
|
|
|
void ModelManagerInterface::resetCodeModel()
|
|
|
|
{
|
2022-06-20 12:35:13 +02:00
|
|
|
QList<Utils::FilePath> documents;
|
2014-01-23 14:28:31 +01:00
|
|
|
|
|
|
|
{
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
|
|
|
// find all documents currently in the code model
|
2019-10-23 14:03:13 +02:00
|
|
|
for (const Document::Ptr &doc : qAsConst(m_validSnapshot))
|
2014-01-23 14:28:31 +01:00
|
|
|
documents.append(doc->fileName());
|
|
|
|
|
|
|
|
// reset the snapshot
|
2014-02-13 11:11:40 +01:00
|
|
|
m_validSnapshot = Snapshot();
|
|
|
|
m_newestSnapshot = Snapshot();
|
2020-06-23 11:44:37 +02:00
|
|
|
m_scannedPaths.clear();
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// start a reparse thread
|
|
|
|
updateSourceFiles(documents, false);
|
2020-06-23 11:44:37 +02:00
|
|
|
|
|
|
|
// rescan import directories
|
|
|
|
m_shouldScanImports = true;
|
|
|
|
updateImportPaths();
|
2014-01-23 14:28:31 +01:00
|
|
|
}
|
|
|
|
|
qmljs: avoid linking to files in the build directory
cmake creates a consistent uri structure in the build directory.
We use that as import path, but when we find a type in them we should
refer back to the original file, as editing those is dangerous because
any edit are lost with the next build.
To find the original file we use the qrc, as the qrc path is mostly
the same of as the uri path.
It is possible to add prefixes which would make an exact match fail,
so we compare the paths from the right to the left and find the
longest match.
To acheive this:
* QrcParser keeps a reversedResources, so the match from right can be
done efficiently, and provides a longestReverseMatches method
* the model manager keeps a list of all common prefixes of the
application paths (build directories), and identify all files in
build directories
* the method fileToSource identifies the files in the build directory
and tries to find the corresponding source file, warning if he
cannot find it
* fileToSource is used for follow Symbol and find usages
We could use fileToSource much more aggressively, to use to in editor
content for the files in the build directory, increasing the
consistency, but that is a more dangerous change for later.
Fixes: QTCREATORBUG-27173
Change-Id: Iea61b9825e5f6e433a7390cf2de9564b792458a5
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-06-17 15:46:52 +02:00
|
|
|
Utils::FilePath ModelManagerInterface::fileToSource(const Utils::FilePath &path)
|
|
|
|
{
|
|
|
|
if (!path.scheme().isEmpty())
|
|
|
|
return path;
|
|
|
|
for (const Utils::FilePath &p : m_applicationPaths) {
|
|
|
|
if (!p.isEmpty() && path.startsWith(p.path())) {
|
|
|
|
// if it is an applicationPath (i.e. in the build directory)
|
|
|
|
// try to use the path from the build dir as resource path
|
|
|
|
// and recover the path of the corresponding source file
|
|
|
|
QString reducedPath = path.path().mid(p.path().size());
|
|
|
|
QString reversePath(reducedPath);
|
|
|
|
std::reverse(reversePath.begin(), reversePath.end());
|
|
|
|
if (!reversePath.endsWith('/'))
|
|
|
|
reversePath.append('/');
|
|
|
|
QrcParser::MatchResult res;
|
|
|
|
iterateQrcFiles(nullptr,
|
|
|
|
QrcResourceSelector::AllQrcResources,
|
|
|
|
[&](const QrcParser::ConstPtr &qrcFile) {
|
|
|
|
if (!qrcFile)
|
|
|
|
return;
|
|
|
|
QrcParser::MatchResult matchNow = qrcFile->longestReverseMatches(
|
|
|
|
reversePath);
|
|
|
|
|
|
|
|
if (matchNow.matchDepth < res.matchDepth)
|
|
|
|
return;
|
|
|
|
if (matchNow.matchDepth == res.matchDepth) {
|
|
|
|
res.reversedPaths += matchNow.reversedPaths;
|
|
|
|
res.sourceFiles += matchNow.sourceFiles;
|
|
|
|
} else {
|
|
|
|
res = matchNow;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
std::sort(res.sourceFiles.begin(), res.sourceFiles.end());
|
|
|
|
if (!res.sourceFiles.isEmpty()) {
|
|
|
|
return res.sourceFiles.first();
|
|
|
|
}
|
|
|
|
qCWarning(qmljsLog) << "Could not find source file for file" << path
|
|
|
|
<< "in application path" << p;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
2014-01-23 14:28:31 +01:00
|
|
|
} // namespace QmlJS
|