diff --git a/src/plugins/python/CMakeLists.txt b/src/plugins/python/CMakeLists.txt index 7ce8b21e06b..309680913a9 100644 --- a/src/plugins/python/CMakeLists.txt +++ b/src/plugins/python/CMakeLists.txt @@ -1,4 +1,5 @@ add_qtc_plugin(Python + DEPENDS QmlJS PLUGIN_DEPENDS Core LanguageClient ProjectExplorer TextEditor SOURCES python.qrc diff --git a/src/plugins/python/python.qbs b/src/plugins/python/python.qbs index 6d14917bc3d..ba5e6b39e11 100644 --- a/src/plugins/python/python.qbs +++ b/src/plugins/python/python.qbs @@ -4,6 +4,8 @@ QtcPlugin { name: "Python" Depends { name: "Qt.widgets" } + + Depends { name: "QmlJS" } Depends { name: "Utils" } Depends { name: "Core" } diff --git a/src/plugins/python/python_dependencies.pri b/src/plugins/python/python_dependencies.pri index a62bb0e8af8..d49bf49dd74 100644 --- a/src/plugins/python/python_dependencies.pri +++ b/src/plugins/python/python_dependencies.pri @@ -1,6 +1,7 @@ QTC_PLUGIN_NAME = Python QTC_LIB_DEPENDS += \ extensionsystem \ + qmljs \ utils QTC_PLUGIN_DEPENDS += \ coreplugin \ diff --git a/src/plugins/python/pythonproject.cpp b/src/plugins/python/pythonproject.cpp index 3b53292d8c9..319fe245d68 100644 --- a/src/plugins/python/pythonproject.cpp +++ b/src/plugins/python/pythonproject.cpp @@ -46,6 +46,8 @@ #include #include +#include + #include using namespace Core; @@ -80,10 +82,12 @@ public: private: QStringList m_rawFileList; QStringList m_files; + QStringList m_rawQmlImportPathList; + QStringList m_qmlImportPaths; QHash m_rawListEntries; + QHash m_rawQmlImportPathEntries; }; - /** * @brief Provides displayName relative to project node */ @@ -101,6 +105,38 @@ private: QString m_displayName; }; +static QJsonObject readObjJson(const FilePath &projectFile, QString *errorMessage) +{ + QFile file(projectFile.toString()); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + *errorMessage = PythonProject::tr("Unable to open \"%1\" for reading: %2") + .arg(projectFile.toUserOutput(), file.errorString()); + return QJsonObject(); + } + + const QByteArray content = file.readAll(); + + // This assumes the project file is formed with only one field called + // 'files' that has a list associated of the files to include in the project. + if (content.isEmpty()) { + *errorMessage = PythonProject::tr("Unable to read \"%1\": The file is empty.") + .arg(projectFile.toUserOutput()); + return QJsonObject(); + } + + QJsonParseError error; + const QJsonDocument doc = QJsonDocument::fromJson(content, &error); + if (doc.isNull()) { + const int line = content.left(error.offset).count('\n') + 1; + *errorMessage = PythonProject::tr("Unable to parse \"%1\":%2: %3") + .arg(projectFile.toUserOutput()).arg(line) + .arg(error.errorString()); + return QJsonObject(); + } + + return doc.object(); +} + static QStringList readLines(const FilePath &projectFile) { const QString projectFileName = projectFile.fileName(); @@ -127,37 +163,9 @@ static QStringList readLines(const FilePath &projectFile) static QStringList readLinesJson(const FilePath &projectFile, QString *errorMessage) { - const QString projectFileName = projectFile.fileName(); - QStringList lines = { projectFileName }; + QStringList lines = { projectFile.fileName() }; - QFile file(projectFile.toString()); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - *errorMessage = PythonProject::tr("Unable to open \"%1\" for reading: %2") - .arg(projectFile.toUserOutput(), file.errorString()); - return lines; - } - - const QByteArray content = file.readAll(); - - // This assumes the project file is formed with only one field called - // 'files' that has a list associated of the files to include in the project. - if (content.isEmpty()) { - *errorMessage = PythonProject::tr("Unable to read \"%1\": The file is empty.") - .arg(projectFile.toUserOutput()); - return lines; - } - - QJsonParseError error; - const QJsonDocument doc = QJsonDocument::fromJson(content, &error); - if (doc.isNull()) { - const int line = content.left(error.offset).count('\n') + 1; - *errorMessage = PythonProject::tr("Unable to parse \"%1\":%2: %3") - .arg(projectFile.toUserOutput()).arg(line) - .arg(error.errorString()); - return lines; - } - - const QJsonObject obj = doc.object(); + const QJsonObject obj = readObjJson(projectFile, errorMessage); if (obj.contains("files")) { const QJsonValue files = obj.value("files"); const QJsonArray files_array = files.toArray(); @@ -171,6 +179,26 @@ static QStringList readLinesJson(const FilePath &projectFile, QString *errorMess return lines; } +static QStringList readImportPathsJson(const FilePath &projectFile, QString *errorMessage) +{ + QStringList importPaths; + + const QJsonObject obj = readObjJson(projectFile, errorMessage); + if (obj.contains("qmlImportPaths")) { + const QJsonValue dirs = obj.value("qmlImportPaths"); + const QJsonArray dirs_array = dirs.toArray(); + + QSet visited; + + for (const auto &dir : dirs_array) + visited.insert(dir.toString()); + + importPaths.append(Utils::toList(visited)); + } + + return importPaths; +} + class PythonProjectNode : public ProjectNode { public: @@ -211,6 +239,7 @@ static FileType getFileType(const FilePath &f) void PythonBuildSystem::triggerParsing() { ParseGuard guard = guardParsingRun(); + parse(); const QDir baseDir(projectDirectory().toString()); @@ -235,6 +264,18 @@ void PythonBuildSystem::triggerParsing() setApplicationTargets(appTargets); + auto modelManager = QmlJS::ModelManagerInterface::instance(); + if (modelManager) { + auto projectInfo = modelManager->defaultProjectInfoForProject(project()); + + for (const QString &importPath : m_qmlImportPaths) { + const Utils::FilePath filePath = Utils::FilePath::fromString(importPath); + projectInfo.importPaths.maybeInsert(filePath, QmlJS::Dialect::Qml); + } + + modelManager->updateProjectInfo(projectInfo, project()); + } + guard.markAsSuccess(); emitBuildSystemUpdated(); @@ -355,6 +396,8 @@ bool PythonBuildSystem::renameFile(Node *, const QString &filePath, const QStrin void PythonBuildSystem::parse() { m_rawListEntries.clear(); + m_rawQmlImportPathEntries.clear(); + const FilePath filePath = projectFilePath(); // The PySide project file is JSON based if (filePath.endsWith(".pyproject")) { @@ -362,13 +405,19 @@ void PythonBuildSystem::parse() m_rawFileList = readLinesJson(filePath, &errorMessage); if (!errorMessage.isEmpty()) MessageManager::write(errorMessage); - } - // To keep compatibility with PyQt we keep the compatibility with plain - // text files as project files. - else if (filePath.endsWith(".pyqtc")) + + errorMessage.clear(); + m_rawQmlImportPathList = readImportPathsJson(filePath, &errorMessage); + if (!errorMessage.isEmpty()) + MessageManager::write(errorMessage); + } else if (filePath.endsWith(".pyqtc")) { + // To keep compatibility with PyQt we keep the compatibility with plain + // text files as project files. m_rawFileList = readLines(filePath); + } m_files = processEntries(m_rawFileList, &m_rawListEntries); + m_qmlImportPaths = processEntries(m_rawQmlImportPathList, &m_rawQmlImportPathEntries); } /** diff --git a/tests/manual/python/pyproject/imports/Charts/chartbackground.qml b/tests/manual/python/pyproject/imports/Charts/chartbackground.qml new file mode 100644 index 00000000000..671048f9b50 --- /dev/null +++ b/tests/manual/python/pyproject/imports/Charts/chartbackground.qml @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.12 + +Rectangle { + width: 48 + height: 48 + border { + width: 1 + color: "black" + } + color: "lightgrey" +} diff --git a/tests/manual/python/pyproject/imports/Charts/qmldir b/tests/manual/python/pyproject/imports/Charts/qmldir new file mode 100644 index 00000000000..9e9f931d1e0 --- /dev/null +++ b/tests/manual/python/pyproject/imports/Charts/qmldir @@ -0,0 +1,2 @@ +module Charts +ChartBackground 1.0 ./chartbackground.qml diff --git a/tests/manual/python/pyproject/main.py b/tests/manual/python/pyproject/main.py new file mode 100644 index 00000000000..fba69c38d5e --- /dev/null +++ b/tests/manual/python/pyproject/main.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +############################################################################# +## +## Copyright (C) 2020 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of Qt for Python. +## +## $QT_BEGIN_LICENSE:LGPL$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 3 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL3 included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 3 requirements +## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 2.0 or (at your option) the GNU General +## Public license version 3 or any later version approved by the KDE Free +## Qt Foundation. The licenses are as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +## 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-2.0.html and +## https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +import os +import sys + +from PySide2.QtGui import QGuiApplication +from PySide2.QtQml import QQmlApplicationEngine + + +if __name__ == "__main__": + app = QGuiApplication(sys.argv) + + engine = QQmlApplicationEngine() + engine.load(os.path.join(os.path.dirname(__file__), "main.qml")) + + if not engine.rootObjects(): + sys.exit(-1) + + sys.exit(app.exec_()) diff --git a/tests/manual/python/pyproject/main.qml b/tests/manual/python/pyproject/main.qml new file mode 100644 index 00000000000..efa0cc8de7e --- /dev/null +++ b/tests/manual/python/pyproject/main.qml @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.12 +import QtQuick.Window 2.12 +import Charts 1.0 as Charts // Qt Creator displays "QML module not found (Charts)." + // if qmlImportPaths value is missing from pyproject.pyproject file. + +Window { + width: 640 + height: 480 + visible: true + title: qsTr("pyproject") + + Charts.ChartBackground { // Syntax highlight and code completion doesn't work + // if qmlImportPaths value is missing from pyproject.pyproject file. + anchors.centerIn: parent + } +} diff --git a/tests/manual/python/pyproject/pyproject.pyproject b/tests/manual/python/pyproject/pyproject.pyproject new file mode 100644 index 00000000000..17839669dab --- /dev/null +++ b/tests/manual/python/pyproject/pyproject.pyproject @@ -0,0 +1,9 @@ +{ + "files": [ + "main.py", + "main.qml" + ], + "qmlImportPaths": [ + "./imports" + ] +}