// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #pragma once #include "qmljs_global.h" #include "qmljsbundle.h" #include "qmljsdocument.h" #include "qmljsdialect.h" #include #include #include #include #include #include #include #include #include #include #include #include #include QT_FORWARD_DECLARE_CLASS(QTimer) namespace ProjectExplorer { class Project; } namespace QmlJS { class Snapshot; class PluginDumper; class QMLJS_EXPORT ModelManagerInterface : public QObject { Q_OBJECT Q_DISABLE_COPY(ModelManagerInterface) public: ModelManagerInterface(ModelManagerInterface &&) = delete; ModelManagerInterface &operator=(ModelManagerInterface &&) = delete; enum QrcResourceSelector { ActiveQrcResources, AllQrcResources }; struct ProjectInfo { QPointer project; QList sourceFiles; PathsAndLanguages importPaths; QList activeResourceFiles; QList allResourceFiles; QList generatedQrcFiles; QHash resourceFileContents; QList applicationDirectories; QHash moduleMappings; // E.g.: QtQuick.Controls -> MyProject.MyControls // whether trying to run qmldump makes sense bool tryQmlDump = false; bool qmlDumpHasRelocatableFlag = true; Utils::FilePath qmlDumpPath; Utils::Environment qmlDumpEnvironment; Utils::FilePath qtQmlPath; Utils::FilePath qmllsPath; QString qtVersionString; QmlJS::QmlLanguageBundles activeBundle; QmlJS::QmlLanguageBundles extendedBundle; }; class WorkingCopy { public: using Table = QHash>; void insert(const Utils::FilePath &fileName, const QString &source, int revision = 0) { m_elements.insert(fileName, {source, revision}); } bool contains(const Utils::FilePath &fileName) const { return m_elements.contains(fileName); } QString source(const Utils::FilePath &fileName) const { return m_elements.value(fileName).first; } QPair get(const Utils::FilePath &fileName) const { return m_elements.value(fileName); } Table all() const { return m_elements; } private: Table m_elements; }; struct CppData { QList exportedTypes; QHash contextProperties; }; using CppDataHash = QHash; public: ModelManagerInterface(QObject *parent = nullptr); ~ModelManagerInterface() override; static Dialect guessLanguageOfFile(const Utils::FilePath &fileName); static QStringList globPatternsForLanguages(const QList &languages); static ModelManagerInterface *instance(); static ModelManagerInterface *instanceForFuture(const QFuture &future); static void writeWarning(const QString &msg); static WorkingCopy workingCopy(); static Utils::FilePath qmllsForBinPath(const Utils::FilePath &binPath, const QVersionNumber &v); QmlJS::Snapshot snapshot() const; QmlJS::Snapshot newestSnapshot() const; QThreadPool *threadPool(); QSet scannedPaths() const; void removeFromScannedPaths(const PathsAndLanguages &pathsAndLanguages); void activateScan(); void updateSourceFiles(const QList &files, bool emitDocumentOnDiskChanged); void fileChangedOnDisk(const Utils::FilePath &path); void removeFiles(const QList &files); QStringList qrcPathsForFile(const Utils::FilePath &file, const QLocale *locale = nullptr, ProjectExplorer::Project *project = nullptr, QrcResourceSelector resources = AllQrcResources); QStringList filesAtQrcPath(const QString &path, const QLocale *locale = nullptr, ProjectExplorer::Project *project = nullptr, QrcResourceSelector resources = AllQrcResources); QMap filesInQrcPath(const QString &path, const QLocale *locale = nullptr, ProjectExplorer::Project *project = nullptr, bool addDirs = false, QrcResourceSelector resources = AllQrcResources); Utils::FilePath fileToSource(const Utils::FilePath &file); QList projectInfos() const; bool containsProject(ProjectExplorer::Project *project) const; ProjectInfo projectInfo(ProjectExplorer::Project *project) const; void updateProjectInfo(const ProjectInfo &pinfo, ProjectExplorer::Project *p); void updateDocument(const QmlJS::Document::Ptr& doc); void updateLibraryInfo(const Utils::FilePath &path, const QmlJS::LibraryInfo &info); void emitDocumentChangedOnDisk(QmlJS::Document::Ptr doc); void updateQrcFile(const Utils::FilePath &path); ProjectInfo projectInfoForPath(const Utils::FilePath &path) const; QList allProjectInfosForPath(const Utils::FilePath &path) const; QList importPathsNames() const; QmlJS::QmlLanguageBundles activeBundles() const; QmlJS::QmlLanguageBundles extendedBundles() const; void loadPluginTypes(const Utils::FilePath &libraryPath, const Utils::FilePath &importPath, const QString &importUri, const QString &importVersion); CppDataHash cppData() const; LibraryInfo builtins(const Document::Ptr &doc) const; ViewerContext completeVContext(const ViewerContext &vCtx, const Document::Ptr &doc = Document::Ptr(nullptr)) const; ViewerContext defaultVContext(Dialect language = Dialect::Qml, const Document::Ptr &doc = Document::Ptr(nullptr), bool autoComplete = true) const; ViewerContext projectVContext(Dialect language, const Document::Ptr &doc) const; void setDefaultVContext(const ViewerContext &vContext); virtual ProjectInfo defaultProjectInfo() const; virtual ProjectInfo defaultProjectInfoForProject(ProjectExplorer::Project *project, const Utils::FilePaths &hiddenRccFolders) const; // Blocks until all parsing threads are done. Use for testing only! void test_joinAllThreads(); template void addFuture(const QFuture &future) { addFuture(QFuture(future)); } void addFuture(const QFuture &future); QmlJS::Document::Ptr ensuredGetDocumentForPath(const Utils::FilePath &filePath); static void importScan(const WorkingCopy &workingCopy, const PathsAndLanguages &paths, ModelManagerInterface *modelManager, bool emitDocChanged, bool libOnly = true, bool forceRescan = false); static void importScanAsync(QPromise &promise, const WorkingCopy& workingCopyInternal, const PathsAndLanguages& paths, ModelManagerInterface *modelManager, bool emitDocChanged, bool libOnly = true, bool forceRescan = false); virtual void resetCodeModel(); void removeProjectInfo(ProjectExplorer::Project *project); void maybeQueueCppQmlTypeUpdate(const CPlusPlus::Document::Ptr &doc); QFuture refreshSourceFiles(const QList &sourceFiles, bool emitDocumentOnDiskChanged); signals: void documentUpdated(QmlJS::Document::Ptr doc); void documentChangedOnDisk(QmlJS::Document::Ptr doc); void aboutToRemoveFiles(const QList &files); void libraryInfoUpdated(const Utils::FilePath &path, const QmlJS::LibraryInfo &info); void projectInfoUpdated(const ProjectInfo &pinfo); void projectPathChanged(const Utils::FilePath &projectPath); protected: Q_INVOKABLE void queueCppQmlTypeUpdate(const CPlusPlus::Document::Ptr &doc, bool scan); Q_INVOKABLE void asyncReset(); virtual void startCppQmlTypeUpdate(); QMutex *mutex() const; virtual QHash languageForSuffix() const; virtual void writeMessageInternal(const QString &msg) const; virtual WorkingCopy workingCopyInternal() const; virtual void addTaskInternal(const QFuture &result, const QString &msg, const char *taskId) const; static void parseLoop(QSet &scannedPaths, QSet &newLibraries, const WorkingCopy &workingCopyInternal, QList files, ModelManagerInterface *modelManager, QmlJS::Dialect mainLanguage, bool emitDocChangedOnDisk, const std::function &reportProgress); static void parse(QPromise &promise, const WorkingCopy &workingCopyInternal, QList files, ModelManagerInterface *modelManager, QmlJS::Dialect mainLanguage, bool emitDocChangedOnDisk); static void updateCppQmlTypes(QPromise &promise, ModelManagerInterface *qmlModelManager, const CPlusPlus::Snapshot &snapshot, const QHash> &documents); void maybeScan(const PathsAndLanguages &importPaths); void updateImportPaths(); void loadQmlTypeDescriptionsInternal(const QString &path); void setDefaultProject(const ProjectInfo &pInfo, ProjectExplorer::Project *p); void cancelAllThreads(); private: void joinAllThreads(bool cancelOnWait = false); void iterateQrcFiles(ProjectExplorer::Project *project, QrcResourceSelector resources, const std::function &callback); ViewerContext getVContext(const ViewerContext &vCtx, const Document::Ptr &doc, bool limitToProject) const; struct SyncedData { SyncedData(const QList &defaultImportPaths); QmlJS::Snapshot m_validSnapshot; QmlJS::Snapshot m_newestSnapshot; PathsAndLanguages m_allImportPaths; QList m_applicationPaths; QList m_defaultImportPaths; QmlJS::QmlLanguageBundles m_activeBundles; QmlJS::QmlLanguageBundles m_extendedBundles; QHash m_defaultVContexts; bool m_shouldScanImports = false; QSet m_scannedPaths; ProjectExplorer::Project *m_defaultProject = nullptr; ProjectInfo m_defaultProjectInfo; QHash m_projects; QMultiHash m_fileToProject; }; Utils::SynchronizedValue m_syncedData; QTimer *m_updateCppQmlTypesTimer = nullptr; QTimer *m_asyncResetTimer = nullptr; QHash> m_queuedCppDocuments; QFuture m_cppQmlTypesUpdater; Utils::QrcCache m_qrcCache; QHash m_qrcContents; struct SyncedCppData { CppDataHash m_cppDataHash; QHash> m_cppDeclarationFiles; }; Utils::SynchronizedValue m_syncedCppData; PluginDumper *m_pluginDumper = nullptr; mutable QMutex m_futuresMutex; Utils::FutureSynchronizer m_futureSynchronizer; bool m_indexerDisabled = false; QThreadPool m_threadPool; private: QList importPathsNames(const SyncedData &lockedData) const; static bool findNewQmlApplicationInPath( const Utils::FilePath &path, const Snapshot &snapshot, ModelManagerInterface *modelManager, QSet *newLibraries, Utils::SynchronizedValue::unique_lock &lock); static void findNewLibraryImports(const Document::Ptr &doc, const Snapshot &snapshot, ModelManagerInterface *modelManager, Utils::FilePaths *importedFiles, QSet *scannedPaths, QSet *newLibraries, Utils::SynchronizedValue::unique_lock *lock); static bool findNewQmlLibraryInPath(const Utils::FilePath &path, const Snapshot &snapshot, ModelManagerInterface *modelManager, QList *importedFiles, QSet *scannedPaths, QSet *newLibraries, bool ignoreMissing, Utils::SynchronizedValue::unique_lock *lock); void updateLibraryInfo(const Utils::FilePath &path, const QmlJS::LibraryInfo &info, Utils::SynchronizedValue::unique_lock &lock); }; } // namespace QmlJS