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
|
||||
SOURCES
|
||||
pipsupport.cpp pipsupport.h
|
||||
pyside.cpp pyside.h
|
||||
python.qrc
|
||||
pythonconstants.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: [
|
||||
"pipsupport.cpp",
|
||||
"pipsupport.h",
|
||||
"pyside.cpp",
|
||||
"pyside.h",
|
||||
"python.qrc",
|
||||
"pythonconstants.h",
|
||||
"pythoneditor.cpp",
|
||||
|
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "pythoneditor.h"
|
||||
|
||||
#include "pyside.h"
|
||||
#include "pythonconstants.h"
|
||||
#include "pythonhighlighter.h"
|
||||
#include "pythonindenter.h"
|
||||
@@ -118,6 +119,7 @@ public:
|
||||
return;
|
||||
|
||||
PyLSConfigureAssistant::instance()->openDocumentWithPython(python, this);
|
||||
PySideInstaller::instance()->checkPySideInstallation(python, this);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "pythonrunconfiguration.h"
|
||||
|
||||
#include "pyside.h"
|
||||
#include "pythonconstants.h"
|
||||
#include "pythonlanguageclient.h"
|
||||
#include "pythonproject.h"
|
||||
@@ -244,7 +245,7 @@ PythonRunConfiguration::PythonRunConfiguration(Target *target, Utils::Id id)
|
||||
auto interpreterAspect = addAspect<InterpreterAspect>();
|
||||
interpreterAspect->setSettingsKey("PythonEditor.RunConfiguation.Interpreter");
|
||||
connect(interpreterAspect, &InterpreterAspect::changed,
|
||||
this, &PythonRunConfiguration::updateLanguageServer);
|
||||
this, &PythonRunConfiguration::interpreterChanged);
|
||||
|
||||
connect(PythonSettings::instance(), &PythonSettings::interpretersChanged,
|
||||
interpreterAspect, &InterpreterAspect::updateInterpreters);
|
||||
@@ -292,7 +293,7 @@ PythonRunConfiguration::PythonRunConfiguration(Target *target, Utils::Id id)
|
||||
connect(target, &Target::buildSystemUpdated, this, &RunConfiguration::update);
|
||||
}
|
||||
|
||||
void PythonRunConfiguration::updateLanguageServer()
|
||||
void PythonRunConfiguration::interpreterChanged()
|
||||
{
|
||||
using namespace LanguageClient;
|
||||
|
||||
@@ -300,8 +301,10 @@ void PythonRunConfiguration::updateLanguageServer()
|
||||
|
||||
for (FilePath &file : project()->files(Project::AllFiles)) {
|
||||
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);
|
||||
PySideInstaller::instance()->checkPySideInstallation(python, document);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -45,7 +45,7 @@ public:
|
||||
QString interpreter() const;
|
||||
|
||||
private:
|
||||
void updateLanguageServer();
|
||||
void interpreterChanged();
|
||||
|
||||
bool supportsDebugger() const;
|
||||
QString mainScript() const;
|
||||
|
@@ -549,6 +549,12 @@ Interpreter PythonSettings::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 Python
|
||||
|
||||
|
@@ -62,6 +62,7 @@ public:
|
||||
|
||||
static QList<Interpreter> interpreters();
|
||||
static Interpreter defaultInterpreter();
|
||||
static Interpreter interpreter(const QString &interpreterId);
|
||||
static void setInterpreter(const QList<Interpreter> &interpreters, const QString &defaultId);
|
||||
static void addInterpreter(const Interpreter &interpreter, bool isDefault = false);
|
||||
static PythonSettings *instance();
|
||||
|
Reference in New Issue
Block a user