forked from qt-creator/qt-creator
Python: add PySide installation check on document open
Checks if the document imports PySide and whether the selected python interpreter can find a PySide installation. If not show a global info bar that can install PySide via pip like the python lsp server. Task-number: PYSIDE-1742 Change-Id: I02c0d5f6eb268f3d8826d4fb9d9ec3c7c48b8638 Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
@@ -3,6 +3,7 @@ add_qtc_plugin(Python
|
|||||||
PLUGIN_DEPENDS Core LanguageClient ProjectExplorer TextEditor
|
PLUGIN_DEPENDS Core LanguageClient ProjectExplorer TextEditor
|
||||||
SOURCES
|
SOURCES
|
||||||
pipsupport.cpp pipsupport.h
|
pipsupport.cpp pipsupport.h
|
||||||
|
pyside.cpp pyside.h
|
||||||
python.qrc
|
python.qrc
|
||||||
pythonconstants.h
|
pythonconstants.h
|
||||||
pythoneditor.cpp pythoneditor.h
|
pythoneditor.cpp pythoneditor.h
|
||||||
|
186
src/plugins/python/pyside.cpp
Normal file
186
src/plugins/python/pyside.cpp
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "pyside.h"
|
||||||
|
|
||||||
|
#include "pythonplugin.h"
|
||||||
|
#include "pythonproject.h"
|
||||||
|
#include "pythonrunconfiguration.h"
|
||||||
|
#include "pythonsettings.h"
|
||||||
|
#include "pythonutils.h"
|
||||||
|
|
||||||
|
#include <coreplugin/icore.h>
|
||||||
|
#include <projectexplorer/target.h>
|
||||||
|
#include <texteditor/textdocument.h>
|
||||||
|
#include <utils/algorithm.h>
|
||||||
|
#include <utils/infobar.h>
|
||||||
|
#include <utils/runextensions.h>
|
||||||
|
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QTextCursor>
|
||||||
|
|
||||||
|
using namespace Utils;
|
||||||
|
|
||||||
|
namespace Python {
|
||||||
|
namespace Internal {
|
||||||
|
|
||||||
|
static constexpr char installPySideInfoBarId[] = "Python::InstallPySide";
|
||||||
|
|
||||||
|
PySideInstaller *PySideInstaller::instance()
|
||||||
|
{
|
||||||
|
static PySideInstaller *instance = new PySideInstaller;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PySideInstaller::checkPySideInstallation(const Utils::FilePath &python,
|
||||||
|
TextEditor::TextDocument *document)
|
||||||
|
{
|
||||||
|
document->infoBar()->removeInfo(installPySideInfoBarId);
|
||||||
|
const QString pySide = importedPySide(document->plainText());
|
||||||
|
if (pySide == "PySide2" || pySide == "PySide6")
|
||||||
|
runPySideChecker(python, pySide, document);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PySideInstaller::missingPySideInstallation(const Utils::FilePath &pythonPath,
|
||||||
|
const QString &pySide)
|
||||||
|
{
|
||||||
|
QTC_ASSERT(!pySide.isEmpty(), return false);
|
||||||
|
static QMap<FilePath, QSet<QString>> pythonWithPyside;
|
||||||
|
if (pythonWithPyside[pythonPath].contains(pySide))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
QtcProcess pythonProcess;
|
||||||
|
const CommandLine importPySideCheck(pythonPath, {"-c", "import " + pySide});
|
||||||
|
pythonProcess.setCommand(importPySideCheck);
|
||||||
|
pythonProcess.runBlocking();
|
||||||
|
const bool missing = pythonProcess.result() != ProcessResult::FinishedWithSuccess;
|
||||||
|
if (!missing)
|
||||||
|
pythonWithPyside[pythonPath].insert(pySide);
|
||||||
|
return missing;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PySideInstaller::importedPySide(const QString &text)
|
||||||
|
{
|
||||||
|
static QRegularExpression importScanner("^\\s*(import|from)\\s+(PySide\\d)",
|
||||||
|
QRegularExpression::MultilineOption);
|
||||||
|
const QRegularExpressionMatch match = importScanner.match(text);
|
||||||
|
return match.captured(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
PySideInstaller::PySideInstaller()
|
||||||
|
: QObject(PythonPlugin::instance())
|
||||||
|
{}
|
||||||
|
|
||||||
|
void PySideInstaller::installPyside(const Utils::FilePath &python,
|
||||||
|
const QString &pySide,
|
||||||
|
TextEditor::TextDocument *document)
|
||||||
|
{
|
||||||
|
document->infoBar()->removeInfo(installPySideInfoBarId);
|
||||||
|
|
||||||
|
auto install = new PipInstallTask(python);
|
||||||
|
connect(install, &PipInstallTask::finished, install, &QObject::deleteLater);
|
||||||
|
install->setPackage(PipPackage(pySide));
|
||||||
|
install->run();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PySideInstaller::changeInterpreter(const QString &interpreterId,
|
||||||
|
PythonRunConfiguration *runConfig)
|
||||||
|
{
|
||||||
|
if (runConfig)
|
||||||
|
runConfig->setInterpreter(PythonSettings::interpreter(interpreterId));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PySideInstaller::handlePySideMissing(const FilePath &python,
|
||||||
|
const QString &pySide,
|
||||||
|
TextEditor::TextDocument *document)
|
||||||
|
{
|
||||||
|
if (!document || !document->infoBar()->canInfoBeAdded(installPySideInfoBarId))
|
||||||
|
return;
|
||||||
|
const QString message = tr("%1 installation missing for %2 (%3)")
|
||||||
|
.arg(pySide, pythonName(python), python.toUserOutput());
|
||||||
|
InfoBarEntry info(installPySideInfoBarId, message, InfoBarEntry::GlobalSuppression::Enabled);
|
||||||
|
auto installCallback = [=]() { installPyside(python, pySide, document); };
|
||||||
|
const QString installTooltip = tr("Install %1 for %2 using pip package installer.")
|
||||||
|
.arg(pySide, python.toUserOutput());
|
||||||
|
info.addCustomButton(tr("Install"), installCallback, installTooltip);
|
||||||
|
|
||||||
|
if (PythonProject *project = pythonProjectForFile(document->filePath())) {
|
||||||
|
if (ProjectExplorer::Target *target = project->activeTarget()) {
|
||||||
|
if (auto runConfiguration = qobject_cast<PythonRunConfiguration *>(
|
||||||
|
target->activeRunConfiguration())) {
|
||||||
|
const QList<InfoBarEntry::ComboInfo> interpreters = Utils::transform(
|
||||||
|
PythonSettings::interpreters(), [](const Interpreter &interpreter) {
|
||||||
|
return InfoBarEntry::ComboInfo{interpreter.name, interpreter.id};
|
||||||
|
});
|
||||||
|
auto interpreterChangeCallback =
|
||||||
|
[=, rc = QPointer<PythonRunConfiguration>(runConfiguration)](
|
||||||
|
const InfoBarEntry::ComboInfo &info) {
|
||||||
|
changeInterpreter(info.data.toString(), rc);
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto isCurrentInterpreter
|
||||||
|
= Utils::equal(&InfoBarEntry::ComboInfo::data,
|
||||||
|
QVariant(runConfiguration->interpreter().id));
|
||||||
|
const QString switchTooltip = tr("Switch the Python interpreter for %1")
|
||||||
|
.arg(runConfiguration->displayName());
|
||||||
|
info.setComboInfo(interpreters,
|
||||||
|
interpreterChangeCallback,
|
||||||
|
switchTooltip,
|
||||||
|
Utils::indexOf(interpreters, isCurrentInterpreter));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document->infoBar()->addInfo(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PySideInstaller::runPySideChecker(const Utils::FilePath &python,
|
||||||
|
const QString &pySide,
|
||||||
|
TextEditor::TextDocument *document)
|
||||||
|
{
|
||||||
|
using CheckPySideWatcher = QFutureWatcher<bool>;
|
||||||
|
|
||||||
|
QPointer<CheckPySideWatcher> watcher = new CheckPySideWatcher();
|
||||||
|
|
||||||
|
// cancel and delete watcher after a 10 second timeout
|
||||||
|
QTimer::singleShot(10000, this, [watcher]() {
|
||||||
|
if (watcher) {
|
||||||
|
watcher->cancel();
|
||||||
|
watcher->deleteLater();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(watcher,
|
||||||
|
&CheckPySideWatcher::resultReadyAt,
|
||||||
|
this,
|
||||||
|
[=, document = QPointer<TextEditor::TextDocument>(document)]() {
|
||||||
|
if (watcher->result())
|
||||||
|
handlePySideMissing(python, pySide, document);
|
||||||
|
watcher->deleteLater();
|
||||||
|
});
|
||||||
|
watcher->setFuture(
|
||||||
|
Utils::runAsync(&missingPySideInstallation, python, pySide));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Internal
|
||||||
|
} // namespace Python
|
67
src/plugins/python/pyside.h
Normal file
67
src/plugins/python/pyside.h
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2022 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.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "pipsupport.h"
|
||||||
|
|
||||||
|
#include <utils/filepath.h>
|
||||||
|
|
||||||
|
#include <QTextDocument>
|
||||||
|
|
||||||
|
namespace TextEditor { class TextDocument; }
|
||||||
|
namespace Python {
|
||||||
|
namespace Internal {
|
||||||
|
|
||||||
|
class PythonRunConfiguration;
|
||||||
|
|
||||||
|
class PySideInstaller : public QObject
|
||||||
|
{
|
||||||
|
Q_DECLARE_TR_FUNCTIONS(Python::Internal::PySideInstaller)
|
||||||
|
public:
|
||||||
|
static PySideInstaller *instance();
|
||||||
|
void checkPySideInstallation(const Utils::FilePath &python, TextEditor::TextDocument *document);
|
||||||
|
|
||||||
|
private:
|
||||||
|
PySideInstaller();
|
||||||
|
|
||||||
|
void installPyside(const Utils::FilePath &python,
|
||||||
|
const QString &pySide, TextEditor::TextDocument *document);
|
||||||
|
void changeInterpreter(const QString &interpreterId, PythonRunConfiguration *runConfig);
|
||||||
|
void handlePySideMissing(const Utils::FilePath &python,
|
||||||
|
const QString &pySide,
|
||||||
|
TextEditor::TextDocument *document);
|
||||||
|
|
||||||
|
void runPySideChecker(const Utils::FilePath &python,
|
||||||
|
const QString &pySide,
|
||||||
|
TextEditor::TextDocument *document);
|
||||||
|
static bool missingPySideInstallation(const Utils::FilePath &python, const QString &pySide);
|
||||||
|
static QString importedPySide(const QString &text);
|
||||||
|
|
||||||
|
QHash<Utils::FilePath, QList<TextEditor::TextDocument *>> m_infoBarEntries;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Internal
|
||||||
|
} // namespace Python
|
@@ -19,6 +19,8 @@ QtcPlugin {
|
|||||||
files: [
|
files: [
|
||||||
"pipsupport.cpp",
|
"pipsupport.cpp",
|
||||||
"pipsupport.h",
|
"pipsupport.h",
|
||||||
|
"pyside.cpp",
|
||||||
|
"pyside.h",
|
||||||
"python.qrc",
|
"python.qrc",
|
||||||
"pythonconstants.h",
|
"pythonconstants.h",
|
||||||
"pythoneditor.cpp",
|
"pythoneditor.cpp",
|
||||||
|
@@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
#include "pythoneditor.h"
|
#include "pythoneditor.h"
|
||||||
|
|
||||||
|
#include "pyside.h"
|
||||||
#include "pythonconstants.h"
|
#include "pythonconstants.h"
|
||||||
#include "pythonhighlighter.h"
|
#include "pythonhighlighter.h"
|
||||||
#include "pythonindenter.h"
|
#include "pythonindenter.h"
|
||||||
@@ -118,6 +119,7 @@ public:
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
PyLSConfigureAssistant::instance()->openDocumentWithPython(python, this);
|
PyLSConfigureAssistant::instance()->openDocumentWithPython(python, this);
|
||||||
|
PySideInstaller::instance()->checkPySideInstallation(python, this);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
#include "pythonrunconfiguration.h"
|
#include "pythonrunconfiguration.h"
|
||||||
|
|
||||||
|
#include "pyside.h"
|
||||||
#include "pythonconstants.h"
|
#include "pythonconstants.h"
|
||||||
#include "pythonlanguageclient.h"
|
#include "pythonlanguageclient.h"
|
||||||
#include "pythonproject.h"
|
#include "pythonproject.h"
|
||||||
@@ -244,7 +245,7 @@ PythonRunConfiguration::PythonRunConfiguration(Target *target, Utils::Id id)
|
|||||||
auto interpreterAspect = addAspect<InterpreterAspect>();
|
auto interpreterAspect = addAspect<InterpreterAspect>();
|
||||||
interpreterAspect->setSettingsKey("PythonEditor.RunConfiguation.Interpreter");
|
interpreterAspect->setSettingsKey("PythonEditor.RunConfiguation.Interpreter");
|
||||||
connect(interpreterAspect, &InterpreterAspect::changed,
|
connect(interpreterAspect, &InterpreterAspect::changed,
|
||||||
this, &PythonRunConfiguration::updateLanguageServer);
|
this, &PythonRunConfiguration::interpreterChanged);
|
||||||
|
|
||||||
connect(PythonSettings::instance(), &PythonSettings::interpretersChanged,
|
connect(PythonSettings::instance(), &PythonSettings::interpretersChanged,
|
||||||
interpreterAspect, &InterpreterAspect::updateInterpreters);
|
interpreterAspect, &InterpreterAspect::updateInterpreters);
|
||||||
@@ -292,7 +293,7 @@ PythonRunConfiguration::PythonRunConfiguration(Target *target, Utils::Id id)
|
|||||||
connect(target, &Target::buildSystemUpdated, this, &RunConfiguration::update);
|
connect(target, &Target::buildSystemUpdated, this, &RunConfiguration::update);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PythonRunConfiguration::updateLanguageServer()
|
void PythonRunConfiguration::interpreterChanged()
|
||||||
{
|
{
|
||||||
using namespace LanguageClient;
|
using namespace LanguageClient;
|
||||||
|
|
||||||
@@ -300,8 +301,10 @@ void PythonRunConfiguration::updateLanguageServer()
|
|||||||
|
|
||||||
for (FilePath &file : project()->files(Project::AllFiles)) {
|
for (FilePath &file : project()->files(Project::AllFiles)) {
|
||||||
if (auto document = TextEditor::TextDocument::textDocumentForFilePath(file)) {
|
if (auto document = TextEditor::TextDocument::textDocumentForFilePath(file)) {
|
||||||
if (document->mimeType() == Constants::C_PY_MIMETYPE)
|
if (document->mimeType() == Constants::C_PY_MIMETYPE) {
|
||||||
PyLSConfigureAssistant::instance()->openDocumentWithPython(python, document);
|
PyLSConfigureAssistant::instance()->openDocumentWithPython(python, document);
|
||||||
|
PySideInstaller::instance()->checkPySideInstallation(python, document);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -45,7 +45,7 @@ public:
|
|||||||
QString interpreter() const;
|
QString interpreter() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateLanguageServer();
|
void interpreterChanged();
|
||||||
|
|
||||||
bool supportsDebugger() const;
|
bool supportsDebugger() const;
|
||||||
QString mainScript() const;
|
QString mainScript() const;
|
||||||
|
@@ -549,6 +549,12 @@ Interpreter PythonSettings::defaultInterpreter()
|
|||||||
return interpreterOptionsPage().defaultInterpreter();
|
return interpreterOptionsPage().defaultInterpreter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Interpreter PythonSettings::interpreter(const QString &interpreterId)
|
||||||
|
{
|
||||||
|
const QList<Interpreter> interpreters = PythonSettings::interpreters();
|
||||||
|
return Utils::findOrDefault(interpreters, Utils::equal(&Interpreter::id, interpreterId));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
} // namespace Python
|
} // namespace Python
|
||||||
|
|
||||||
|
@@ -62,6 +62,7 @@ public:
|
|||||||
|
|
||||||
static QList<Interpreter> interpreters();
|
static QList<Interpreter> interpreters();
|
||||||
static Interpreter defaultInterpreter();
|
static Interpreter defaultInterpreter();
|
||||||
|
static Interpreter interpreter(const QString &interpreterId);
|
||||||
static void setInterpreter(const QList<Interpreter> &interpreters, const QString &defaultId);
|
static void setInterpreter(const QList<Interpreter> &interpreters, const QString &defaultId);
|
||||||
static void addInterpreter(const Interpreter &interpreter, bool isDefault = false);
|
static void addInterpreter(const Interpreter &interpreter, bool isDefault = false);
|
||||||
static PythonSettings *instance();
|
static PythonSettings *instance();
|
||||||
|
Reference in New Issue
Block a user