forked from qt-creator/qt-creator
Add qmlImportPaths property to .pyproject file
Users should be able to add custom QML import paths for Python/PySide2/PyQt5 projects in Qt Creator in order to get syntax highlighting and code completion for custom QML modules. Fixes: QTCREATORBUG-23679 Change-Id: Iec7c691c4b8709c48a790cd27ac7c6e755967796 Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
add_qtc_plugin(Python
|
add_qtc_plugin(Python
|
||||||
|
DEPENDS QmlJS
|
||||||
PLUGIN_DEPENDS Core LanguageClient ProjectExplorer TextEditor
|
PLUGIN_DEPENDS Core LanguageClient ProjectExplorer TextEditor
|
||||||
SOURCES
|
SOURCES
|
||||||
python.qrc
|
python.qrc
|
||||||
|
@@ -4,6 +4,8 @@ QtcPlugin {
|
|||||||
name: "Python"
|
name: "Python"
|
||||||
|
|
||||||
Depends { name: "Qt.widgets" }
|
Depends { name: "Qt.widgets" }
|
||||||
|
|
||||||
|
Depends { name: "QmlJS" }
|
||||||
Depends { name: "Utils" }
|
Depends { name: "Utils" }
|
||||||
|
|
||||||
Depends { name: "Core" }
|
Depends { name: "Core" }
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
QTC_PLUGIN_NAME = Python
|
QTC_PLUGIN_NAME = Python
|
||||||
QTC_LIB_DEPENDS += \
|
QTC_LIB_DEPENDS += \
|
||||||
extensionsystem \
|
extensionsystem \
|
||||||
|
qmljs \
|
||||||
utils
|
utils
|
||||||
QTC_PLUGIN_DEPENDS += \
|
QTC_PLUGIN_DEPENDS += \
|
||||||
coreplugin \
|
coreplugin \
|
||||||
|
@@ -46,6 +46,8 @@
|
|||||||
#include <coreplugin/icore.h>
|
#include <coreplugin/icore.h>
|
||||||
#include <coreplugin/messagemanager.h>
|
#include <coreplugin/messagemanager.h>
|
||||||
|
|
||||||
|
#include <qmljs/qmljsmodelmanagerinterface.h>
|
||||||
|
|
||||||
#include <utils/fileutils.h>
|
#include <utils/fileutils.h>
|
||||||
|
|
||||||
using namespace Core;
|
using namespace Core;
|
||||||
@@ -80,10 +82,12 @@ public:
|
|||||||
private:
|
private:
|
||||||
QStringList m_rawFileList;
|
QStringList m_rawFileList;
|
||||||
QStringList m_files;
|
QStringList m_files;
|
||||||
|
QStringList m_rawQmlImportPathList;
|
||||||
|
QStringList m_qmlImportPaths;
|
||||||
QHash<QString, QString> m_rawListEntries;
|
QHash<QString, QString> m_rawListEntries;
|
||||||
|
QHash<QString, QString> m_rawQmlImportPathEntries;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Provides displayName relative to project node
|
* @brief Provides displayName relative to project node
|
||||||
*/
|
*/
|
||||||
@@ -101,6 +105,38 @@ private:
|
|||||||
QString m_displayName;
|
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)
|
static QStringList readLines(const FilePath &projectFile)
|
||||||
{
|
{
|
||||||
const QString projectFileName = projectFile.fileName();
|
const QString projectFileName = projectFile.fileName();
|
||||||
@@ -127,37 +163,9 @@ static QStringList readLines(const FilePath &projectFile)
|
|||||||
|
|
||||||
static QStringList readLinesJson(const FilePath &projectFile, QString *errorMessage)
|
static QStringList readLinesJson(const FilePath &projectFile, QString *errorMessage)
|
||||||
{
|
{
|
||||||
const QString projectFileName = projectFile.fileName();
|
QStringList lines = { projectFile.fileName() };
|
||||||
QStringList lines = { projectFileName };
|
|
||||||
|
|
||||||
QFile file(projectFile.toString());
|
const QJsonObject obj = readObjJson(projectFile, errorMessage);
|
||||||
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();
|
|
||||||
if (obj.contains("files")) {
|
if (obj.contains("files")) {
|
||||||
const QJsonValue files = obj.value("files");
|
const QJsonValue files = obj.value("files");
|
||||||
const QJsonArray files_array = files.toArray();
|
const QJsonArray files_array = files.toArray();
|
||||||
@@ -171,6 +179,26 @@ static QStringList readLinesJson(const FilePath &projectFile, QString *errorMess
|
|||||||
return lines;
|
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<QString> visited;
|
||||||
|
|
||||||
|
for (const auto &dir : dirs_array)
|
||||||
|
visited.insert(dir.toString());
|
||||||
|
|
||||||
|
importPaths.append(Utils::toList(visited));
|
||||||
|
}
|
||||||
|
|
||||||
|
return importPaths;
|
||||||
|
}
|
||||||
|
|
||||||
class PythonProjectNode : public ProjectNode
|
class PythonProjectNode : public ProjectNode
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -211,6 +239,7 @@ static FileType getFileType(const FilePath &f)
|
|||||||
void PythonBuildSystem::triggerParsing()
|
void PythonBuildSystem::triggerParsing()
|
||||||
{
|
{
|
||||||
ParseGuard guard = guardParsingRun();
|
ParseGuard guard = guardParsingRun();
|
||||||
|
|
||||||
parse();
|
parse();
|
||||||
|
|
||||||
const QDir baseDir(projectDirectory().toString());
|
const QDir baseDir(projectDirectory().toString());
|
||||||
@@ -235,6 +264,18 @@ void PythonBuildSystem::triggerParsing()
|
|||||||
|
|
||||||
setApplicationTargets(appTargets);
|
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();
|
guard.markAsSuccess();
|
||||||
|
|
||||||
emitBuildSystemUpdated();
|
emitBuildSystemUpdated();
|
||||||
@@ -355,6 +396,8 @@ bool PythonBuildSystem::renameFile(Node *, const QString &filePath, const QStrin
|
|||||||
void PythonBuildSystem::parse()
|
void PythonBuildSystem::parse()
|
||||||
{
|
{
|
||||||
m_rawListEntries.clear();
|
m_rawListEntries.clear();
|
||||||
|
m_rawQmlImportPathEntries.clear();
|
||||||
|
|
||||||
const FilePath filePath = projectFilePath();
|
const FilePath filePath = projectFilePath();
|
||||||
// The PySide project file is JSON based
|
// The PySide project file is JSON based
|
||||||
if (filePath.endsWith(".pyproject")) {
|
if (filePath.endsWith(".pyproject")) {
|
||||||
@@ -362,13 +405,19 @@ void PythonBuildSystem::parse()
|
|||||||
m_rawFileList = readLinesJson(filePath, &errorMessage);
|
m_rawFileList = readLinesJson(filePath, &errorMessage);
|
||||||
if (!errorMessage.isEmpty())
|
if (!errorMessage.isEmpty())
|
||||||
MessageManager::write(errorMessage);
|
MessageManager::write(errorMessage);
|
||||||
}
|
|
||||||
|
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
|
// To keep compatibility with PyQt we keep the compatibility with plain
|
||||||
// text files as project files.
|
// text files as project files.
|
||||||
else if (filePath.endsWith(".pyqtc"))
|
|
||||||
m_rawFileList = readLines(filePath);
|
m_rawFileList = readLines(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
m_files = processEntries(m_rawFileList, &m_rawListEntries);
|
m_files = processEntries(m_rawFileList, &m_rawListEntries);
|
||||||
|
m_qmlImportPaths = processEntries(m_rawQmlImportPathList, &m_rawQmlImportPathEntries);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -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"
|
||||||
|
}
|
2
tests/manual/python/pyproject/imports/Charts/qmldir
Normal file
2
tests/manual/python/pyproject/imports/Charts/qmldir
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
module Charts
|
||||||
|
ChartBackground 1.0 ./chartbackground.qml
|
58
tests/manual/python/pyproject/main.py
Normal file
58
tests/manual/python/pyproject/main.py
Normal file
@@ -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_())
|
41
tests/manual/python/pyproject/main.qml
Normal file
41
tests/manual/python/pyproject/main.qml
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
9
tests/manual/python/pyproject/pyproject.pyproject
Normal file
9
tests/manual/python/pyproject/pyproject.pyproject
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"main.py",
|
||||||
|
"main.qml"
|
||||||
|
],
|
||||||
|
"qmlImportPaths": [
|
||||||
|
"./imports"
|
||||||
|
]
|
||||||
|
}
|
Reference in New Issue
Block a user