Python: add create venv action

The action can be triggered from the interpreter chooser of the editor
toolbar.

Change-Id: Ie23b68a3790525ea02883ef359b357a0d317b2f5
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
This commit is contained in:
David Schulz
2023-01-23 13:43:58 +01:00
parent 5256f08b6d
commit 7d4f123842
5 changed files with 130 additions and 17 deletions

View File

@@ -163,6 +163,7 @@ void PythonEditorWidget::setUserDefinedPython(const Interpreter &interpreter)
}
}
definePythonForDocument(textDocument()->filePath(), interpreter.command);
updateInterpretersSelector();
pythonDocument->checkForPyls();
}
@@ -212,33 +213,51 @@ void PythonEditorWidget::updateInterpretersSelector()
m_interpreters->setText(text);
};
const FilePath currentInterpreter = detectPython(textDocument()->filePath());
const FilePath currentInterpreterPath = detectPython(textDocument()->filePath());
const QList<Interpreter> configuredInterpreters = PythonSettings::interpreters();
bool foundCurrentInterpreter = false;
auto interpretersGroup = new QActionGroup(menu);
interpretersGroup->setExclusive(true);
std::optional<Interpreter> currentInterpreter;
for (const Interpreter &interpreter : configuredInterpreters) {
QAction *action = interpretersGroup->addAction(interpreter.name);
connect(action, &QAction::triggered, this, [this, interpreter]() {
setUserDefinedPython(interpreter);
});
action->setCheckable(true);
if (!foundCurrentInterpreter && interpreter.command == currentInterpreter) {
foundCurrentInterpreter = true;
if (!currentInterpreter && interpreter.command == currentInterpreterPath) {
currentInterpreter = interpreter;
action->setChecked(true);
setButtonText(interpreter.name);
m_interpreters->setToolTip(interpreter.command.toUserOutput());
}
}
menu->addActions(interpretersGroup->actions());
if (!foundCurrentInterpreter) {
if (currentInterpreter.exists())
setButtonText(currentInterpreter.toUserOutput());
if (!currentInterpreter) {
if (currentInterpreterPath.exists())
setButtonText(currentInterpreterPath.toUserOutput());
else
setButtonText(Tr::tr("No Python Selected"));
}
if (!interpretersGroup->actions().isEmpty())
menu->addSeparator();
if (!interpretersGroup->actions().isEmpty()) {
menu->addSeparator();
auto venvAction = menu->addAction(Tr::tr("Create Virtual Environment"));
connect(venvAction,
&QAction::triggered,
this,
[self = QPointer<PythonEditorWidget>(this), currentInterpreter]() {
if (!currentInterpreter)
return;
auto callback = [self](const std::optional<Interpreter> &venvInterpreter) {
if (self && venvInterpreter)
self->setUserDefinedPython(*venvInterpreter);
};
PythonSettings::createVirtualEnvironment(self->textDocument()
->filePath()
.parentDir(),
*currentInterpreter,
callback);
});
}
auto settingsAction = menu->addAction(Tr::tr("Manage Python Interpreters"));
connect(settingsAction, &QAction::triggered, this, []() {
Core::ICore::showOptionsDialog(Constants::C_PYTHONOPTIONS_PAGE_ID);

View File

@@ -6,6 +6,7 @@
#include "pythonconstants.h"
#include "pythonplugin.h"
#include "pythontr.h"
#include "pythonutils.h"
#include <coreplugin/dialogs/ioptionspage.h>
#include <coreplugin/icore.h>
@@ -30,19 +31,22 @@
#include <utils/treemodel.h>
#include <utils/utilsicons.h>
#include <QCheckBox>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QDir>
#include <QFormLayout>
#include <QGroupBox>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLabel>
#include <QPushButton>
#include <QPointer>
#include <QPushButton>
#include <QSettings>
#include <QStackedWidget>
#include <QTreeView>
#include <QWidget>
#include <QVBoxLayout>
#include <QGroupBox>
#include <QCheckBox>
#include <QJsonDocument>
#include <QJsonObject>
#include <QWidget>
using namespace ProjectExplorer;
using namespace Utils;
@@ -69,7 +73,7 @@ static Interpreter createInterpreter(const FilePath &python,
result.name = defaultName;
QDir pythonDir(python.parentDir().toString());
if (pythonDir.exists() && pythonDir.exists("activate") && pythonDir.cdUp())
result.name += QString(" (%1 Virtual Environment)").arg(pythonDir.dirName());
result.name += QString(" (%1)").arg(pythonDir.dirName());
if (!suffix.isEmpty())
result.name += ' ' + suffix;
@@ -769,12 +773,75 @@ void PythonSettings::addInterpreter(const Interpreter &interpreter, bool isDefau
saveSettings();
}
Interpreter PythonSettings::addInterpreter(const FilePath &interpreterPath, bool isDefault)
{
const Interpreter interpreter = createInterpreter(interpreterPath, {});
addInterpreter(interpreter, isDefault);
return interpreter;
}
PythonSettings *PythonSettings::instance()
{
QTC_CHECK(settingsInstance);
return settingsInstance;
}
void PythonSettings::createVirtualEnvironment(
const FilePath &startDirectory,
const Interpreter &defaultInterpreter,
const std::function<void(std::optional<Interpreter>)> &callback)
{
QDialog dialog;
dialog.setModal(true);
auto layout = new QFormLayout(&dialog);
auto interpreters = new QComboBox;
const QString preselectedId = defaultInterpreter.id.isEmpty()
? PythonSettings::defaultInterpreter().id
: defaultInterpreter.id;
for (const Interpreter &interpreter : PythonSettings::interpreters()) {
interpreters->addItem(interpreter.name, interpreter.id);
if (!preselectedId.isEmpty() && interpreter.id == preselectedId)
interpreters->setCurrentIndex(interpreters->count() - 1);
}
layout->addRow(Tr::tr("Python Interpreter"), interpreters);
auto pathChooser = new PathChooser();
pathChooser->setInitialBrowsePathBackup(startDirectory);
pathChooser->setExpectedKind(PathChooser::Directory);
pathChooser->setPromptDialogTitle(Tr::tr("New Python Virtual Environment Directory"));
layout->addRow(Tr::tr("Virtual Environment Directory"), pathChooser);
auto buttons = new QDialogButtonBox(QDialogButtonBox::Cancel);
auto createButton = buttons->addButton(Tr::tr("Create"), QDialogButtonBox::AcceptRole);
createButton->setEnabled(false);
connect(pathChooser,
&PathChooser::validChanged,
createButton,
[createButton](bool valid) { createButton->setEnabled(valid); });
connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
layout->addRow(buttons);
dialog.setLayout(layout);
if (dialog.exec() == QDialog::Rejected) {
callback({});
return;
}
const Interpreter interpreter = PythonSettings::interpreter(
interpreters->currentData().toString());
auto venvDir = pathChooser->filePath();
createVenv(interpreter.command, venvDir, [venvDir, callback](bool success){
std::optional<Interpreter> result;
if (success) {
FilePath venvPython = venvDir.osType() == Utils::OsTypeWindows ? venvDir / "Scripts"
: venvDir / "bin";
venvPython = venvPython.pathAppended("python").withExecutableSuffix();
if (venvPython.exists())
result = PythonSettings::addInterpreter(venvPython);
}
callback(result);
});
}
QList<Interpreter> PythonSettings::detectPythonVenvs(const FilePath &path)
{
QList<Interpreter> result;

View File

@@ -24,12 +24,14 @@ public:
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 Interpreter addInterpreter(const Utils::FilePath &interpreterPath,
bool isDefault = false);
static void setPyLSConfiguration(const QString &configuration);
static bool pylsEnabled();
static void setPylsEnabled(const bool &enabled);
static QString pylsConfiguration();
static PythonSettings *instance();
static void createVirtualEnvironment(const Utils::FilePath &startDirectory, const Interpreter &defaultInterpreter, const std::function<void (std::optional<Interpreter>)> &callback);
static QList<Interpreter> detectPythonVenvs(const Utils::FilePath &path);
signals:

View File

@@ -8,6 +8,7 @@
#include "pythontr.h"
#include <coreplugin/messagemanager.h>
#include <coreplugin/progressmanager/processprogress.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectmanager.h>
@@ -164,4 +165,24 @@ PythonProject *pythonProjectForFile(const FilePath &pythonFile)
return nullptr;
}
void createVenv(const Utils::FilePath &python,
const Utils::FilePath &venvPath,
const std::function<void(bool)> &callback)
{
QTC_ASSERT(python.isExecutableFile(), callback(false); return);
QTC_ASSERT(!venvPath.exists() || venvPath.isDir(), callback(false); return);
const CommandLine command(python, QStringList{"-m", "venv", venvPath.toUserOutput()});
auto process = new QtcProcess;
auto progress = new Core::ProcessProgress(process);
progress->setDisplayName(Tr::tr("Create Python venv"));
QObject::connect(process, &QtcProcess::done, [process, callback](){
callback(process->result() == ProcessResult::FinishedWithSuccess);
process->deleteLater();
});
process->setCommand(command);
process->start();
}
} // Python::Internal

View File

@@ -16,4 +16,8 @@ QString pythonName(const Utils::FilePath &pythonPath);
class PythonProject;
PythonProject *pythonProjectForFile(const Utils::FilePath &pythonFile);
void createVenv(const Utils::FilePath &python,
const Utils::FilePath &venvPath,
const std::function<void(bool)> &callback);
} // Python::Internal