diff options
| author | David Schulz <david.schulz@qt.io> | 2023-01-23 13:43:58 +0100 |
|---|---|---|
| committer | David Schulz <david.schulz@qt.io> | 2023-03-21 05:39:48 +0000 |
| commit | 7d4f123842ea937de4390b2ba783c7c13f9ea55a (patch) | |
| tree | 2a430480e14f39435c80d1b203bcdc61e6f297fc /src/plugins/python | |
| parent | 5256f08b6dcd6fe5abdf80427626b56194ca209a (diff) | |
| download | qt-creator-7d4f123842ea937de4390b2ba783c7c13f9ea55a.tar.gz | |
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>
Diffstat (limited to 'src/plugins/python')
| -rw-r--r-- | src/plugins/python/pythoneditor.cpp | 37 | ||||
| -rw-r--r-- | src/plugins/python/pythonsettings.cpp | 81 | ||||
| -rw-r--r-- | src/plugins/python/pythonsettings.h | 4 | ||||
| -rw-r--r-- | src/plugins/python/pythonutils.cpp | 21 | ||||
| -rw-r--r-- | src/plugins/python/pythonutils.h | 4 |
5 files changed, 130 insertions, 17 deletions
diff --git a/src/plugins/python/pythoneditor.cpp b/src/plugins/python/pythoneditor.cpp index f758ae3d9b..42771fc91a 100644 --- a/src/plugins/python/pythoneditor.cpp +++ b/src/plugins/python/pythoneditor.cpp @@ -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); diff --git a/src/plugins/python/pythonsettings.cpp b/src/plugins/python/pythonsettings.cpp index 9306fe3b6f..f22f0c3c8c 100644 --- a/src/plugins/python/pythonsettings.cpp +++ b/src/plugins/python/pythonsettings.cpp @@ -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; diff --git a/src/plugins/python/pythonsettings.h b/src/plugins/python/pythonsettings.h index 693c732208..2e27e26616 100644 --- a/src/plugins/python/pythonsettings.h +++ b/src/plugins/python/pythonsettings.h @@ -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: diff --git a/src/plugins/python/pythonutils.cpp b/src/plugins/python/pythonutils.cpp index 8635605092..1f89eef5b5 100644 --- a/src/plugins/python/pythonutils.cpp +++ b/src/plugins/python/pythonutils.cpp @@ -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 diff --git a/src/plugins/python/pythonutils.h b/src/plugins/python/pythonutils.h index 8d5b06974e..f3e685b4ae 100644 --- a/src/plugins/python/pythonutils.h +++ b/src/plugins/python/pythonutils.h @@ -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 |
