forked from qt-creator/qt-creator
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:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user