diff --git a/src/plugins/projectexplorer/runconfigurationaspects.cpp b/src/plugins/projectexplorer/runconfigurationaspects.cpp index 32ab75ee0f9..a794680f445 100644 --- a/src/plugins/projectexplorer/runconfigurationaspects.cpp +++ b/src/plugins/projectexplorer/runconfigurationaspects.cpp @@ -806,7 +806,14 @@ void InterpreterAspect::setDefaultInterpreter(const Interpreter &interpreter) void InterpreterAspect::setCurrentInterpreter(const Interpreter &interpreter) { - m_currentId = interpreter.id; + if (m_comboBox) { + const int index = m_interpreters.indexOf(interpreter); + if (index < 0 || index >= m_comboBox->count()) + return; + m_comboBox->setCurrentIndex(index); + } else { + m_currentId = interpreter.id; + } emit changed(); } @@ -853,13 +860,12 @@ void InterpreterAspect::updateComboBox() { int currentIndex = -1; int defaultIndex = -1; - const QString currentId = m_currentId; m_comboBox->clear(); for (const Interpreter &interpreter : std::as_const(m_interpreters)) { int index = m_comboBox->count(); m_comboBox->addItem(interpreter.name); m_comboBox->setItemData(index, interpreter.command.toUserOutput(), Qt::ToolTipRole); - if (interpreter.id == currentId) + if (interpreter.id == m_currentId) currentIndex = index; if (interpreter.id == m_defaultId) defaultIndex = index; diff --git a/src/plugins/python/pyside.cpp b/src/plugins/python/pyside.cpp index 1ca0a9be650..592addd3085 100644 --- a/src/plugins/python/pyside.cpp +++ b/src/plugins/python/pyside.cpp @@ -4,9 +4,7 @@ #include "pyside.h" #include "pipsupport.h" -#include "pythonconstants.h" #include "pythonplugin.h" -#include "pythonproject.h" #include "pythonsettings.h" #include "pythontr.h" #include "pythonutils.h" @@ -116,35 +114,6 @@ void PySideInstaller::handlePySideMissing(const FilePath &python, const QString installTooltip = Tr::tr("Install %1 for %2 using pip package installer.") .arg(pySide, python.toUserOutput()); info.addCustomButton(Tr::tr("Install"), installCallback, installTooltip); - - if (PythonProject *project = pythonProjectForFile(document->filePath())) { - if (ProjectExplorer::Target *target = project->activeTarget()) { - auto runConfiguration = target->activeRunConfiguration(); - if (runConfiguration->id() == Constants::C_PYTHONRUNCONFIGURATION_ID) { - const QList interpreters = Utils::transform( - PythonSettings::interpreters(), [](const Interpreter &interpreter) { - return InfoBarEntry::ComboInfo{interpreter.name, interpreter.id}; - }); - auto interpreterChangeCallback = - [=, rc = QPointer(runConfiguration)]( - const InfoBarEntry::ComboInfo &info) { - changeInterpreter(info.data.toString(), rc); - }; - - auto interpreterAspect = runConfiguration->aspect(); - QTC_ASSERT(interpreterAspect, return); - const QString id = interpreterAspect->currentInterpreter().id; - const auto isCurrentInterpreter = Utils::equal(&InfoBarEntry::ComboInfo::data, - QVariant(id)); - const QString switchTooltip = Tr::tr("Switch the Python interpreter for %1") - .arg(runConfiguration->displayName()); - info.setComboInfo(interpreters, - interpreterChangeCallback, - switchTooltip, - Utils::indexOf(interpreters, isCurrentInterpreter)); - } - } - } document->infoBar()->addInfo(info); } diff --git a/src/plugins/python/pythoneditor.cpp b/src/plugins/python/pythoneditor.cpp index a880272ca76..77768eae359 100644 --- a/src/plugins/python/pythoneditor.cpp +++ b/src/plugins/python/pythoneditor.cpp @@ -14,13 +14,23 @@ #include #include +#include + +#include +#include +#include +#include #include #include #include +#include +#include #include +using namespace ProjectExplorer; +using namespace TextEditor; using namespace Utils; namespace Python::Internal { @@ -60,30 +70,11 @@ static void registerReplAction(QObject *parent) Constants::PYTHON_OPEN_REPL_IMPORT_TOPLEVEL); } -static QWidget *createEditorWidget() -{ - auto widget = new TextEditor::TextEditorWidget; - auto replButton = new QToolButton(widget); - replButton->setProperty("noArrow", true); - replButton->setText(Tr::tr("REPL")); - replButton->setPopupMode(QToolButton::InstantPopup); - replButton->setToolTip(Tr::tr("Open interactive Python. Either importing nothing, " - "importing the current file, or importing everything (*) from the current file.")); - auto menu = new QMenu(replButton); - replButton->setMenu(menu); - menu->addAction(Core::ActionManager::command(Constants::PYTHON_OPEN_REPL)->action()); - menu->addSeparator(); - menu->addAction(Core::ActionManager::command(Constants::PYTHON_OPEN_REPL_IMPORT)->action()); - menu->addAction( - Core::ActionManager::command(Constants::PYTHON_OPEN_REPL_IMPORT_TOPLEVEL)->action()); - widget->insertExtraToolBarWidget(TextEditor::TextEditorWidget::Left, replButton); - return widget; -} - -class PythonDocument : public TextEditor::TextDocument +class PythonDocument : public TextDocument { + Q_OBJECT public: - PythonDocument() : TextEditor::TextDocument(Constants::C_PYTHONEDITOR_ID) + PythonDocument() : TextDocument(Constants::C_PYTHONEDITOR_ID) { connect(PythonSettings::instance(), &PythonSettings::pylsEnabledChanged, @@ -110,6 +101,149 @@ public: } }; +class PythonEditorWidget : public TextEditorWidget +{ +public: + PythonEditorWidget(QWidget *parent = nullptr); + +protected: + void finalizeInitialization() override; + void setUserDefinedPython(const Interpreter &interpreter); + void updateInterpretersSelector(); + +private: + QToolButton *m_interpreters = nullptr; + QList m_projectConnections; +}; + +PythonEditorWidget::PythonEditorWidget(QWidget *parent) : TextEditorWidget(parent) +{ + auto replButton = new QToolButton(this); + replButton->setProperty("noArrow", true); + replButton->setText(Tr::tr("REPL")); + replButton->setPopupMode(QToolButton::InstantPopup); + replButton->setToolTip(Tr::tr("Open interactive Python. Either importing nothing, " + "importing the current file, " + "or importing everything (*) from the current file.")); + auto menu = new QMenu(replButton); + replButton->setMenu(menu); + menu->addAction(Core::ActionManager::command(Constants::PYTHON_OPEN_REPL)->action()); + menu->addSeparator(); + menu->addAction(Core::ActionManager::command(Constants::PYTHON_OPEN_REPL_IMPORT)->action()); + menu->addAction( + Core::ActionManager::command(Constants::PYTHON_OPEN_REPL_IMPORT_TOPLEVEL)->action()); + insertExtraToolBarWidget(TextEditorWidget::Left, replButton); +} + +void PythonEditorWidget::finalizeInitialization() +{ + connect(textDocument(), &TextDocument::filePathChanged, + this, &PythonEditorWidget::updateInterpretersSelector); + connect(PythonSettings::instance(), &PythonSettings::interpretersChanged, + this, &PythonEditorWidget::updateInterpretersSelector); + connect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::fileListChanged, + this, &PythonEditorWidget::updateInterpretersSelector); +} + +void PythonEditorWidget::setUserDefinedPython(const Interpreter &interpreter) +{ + const auto pythonDocument = qobject_cast(textDocument()); + QTC_ASSERT(pythonDocument, return); + FilePath documentPath = pythonDocument->filePath(); + QTC_ASSERT(!documentPath.isEmpty(), return); + if (Project *project = SessionManager::projectForFile(documentPath)) { + if (Target *target = project->activeTarget()) { + if (RunConfiguration *rc = target->activeRunConfiguration()) { + if (auto interpretersAspect= rc->aspect()) { + interpretersAspect->setCurrentInterpreter(interpreter); + return; + } + } + } + } + definePythonForDocument(textDocument()->filePath(), interpreter.command); + pythonDocument->checkForPyls(); +} + +void PythonEditorWidget::updateInterpretersSelector() +{ + if (!m_interpreters) { + m_interpreters = new QToolButton(this); + insertExtraToolBarWidget(TextEditorWidget::Left, m_interpreters); + m_interpreters->setMenu(new QMenu(m_interpreters)); + m_interpreters->setPopupMode(QToolButton::InstantPopup); + m_interpreters->setToolButtonStyle(Qt::ToolButtonTextOnly); + m_interpreters->setProperty("noArrow", true); + } + + QMenu *menu = m_interpreters->menu(); + QTC_ASSERT(menu, return); + menu->clear(); + for (const QMetaObject::Connection &connection : m_projectConnections) + disconnect(connection); + m_projectConnections.clear(); + const FilePath documentPath = textDocument()->filePath(); + if (Project *project = SessionManager::projectForFile(documentPath)) { + m_projectConnections << connect(project, + &Project::activeTargetChanged, + this, + &PythonEditorWidget::updateInterpretersSelector); + if (Target *target = project->activeTarget()) { + m_projectConnections << connect(target, + &Target::activeRunConfigurationChanged, + this, + &PythonEditorWidget::updateInterpretersSelector); + if (RunConfiguration *rc = target->activeRunConfiguration()) { + if (auto interpreterAspect = rc->aspect()) { + m_projectConnections << connect(interpreterAspect, + &InterpreterAspect::changed, + this, + &PythonEditorWidget::updateInterpretersSelector); + } + } + } + } + + auto setButtonText = [this](QString text) { + constexpr int maxTextLength = 25; + if (text.size() > maxTextLength) + text = text.left(maxTextLength - 3) + "..."; + m_interpreters->setText(text); + }; + + const FilePath currentInterpreter = detectPython(textDocument()->filePath()); + const QList configuredInterpreters = PythonSettings::interpreters(); + bool foundCurrentInterpreter = false; + auto interpretersGroup = new QActionGroup(menu); + interpretersGroup->setExclusive(true); + 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; + action->setChecked(true); + setButtonText(interpreter.name); + m_interpreters->setToolTip(interpreter.command.toUserOutput()); + } + } + menu->addActions(interpretersGroup->actions()); + if (!foundCurrentInterpreter) { + if (currentInterpreter.exists()) + setButtonText(currentInterpreter.toUserOutput()); + else + setButtonText(Tr::tr("No Python Selected")); + } + if (!interpretersGroup->actions().isEmpty()) + menu->addSeparator(); + auto settingsAction = menu->addAction(Tr::tr("Manage Python Interpreters")); + connect(settingsAction, &QAction::triggered, this, []() { + Core::ICore::showOptionsDialog(Constants::C_PYTHONOPTIONS_PAGE_ID); + }); +} + PythonEditorFactory::PythonEditorFactory() { registerReplAction(this); @@ -119,13 +253,13 @@ PythonEditorFactory::PythonEditorFactory() QCoreApplication::translate("OpenWith::Editors", Constants::C_EDITOR_DISPLAY_NAME)); addMimeType(Constants::C_PY_MIMETYPE); - setEditorActionHandlers(TextEditor::TextEditorActionHandler::Format - | TextEditor::TextEditorActionHandler::UnCommentSelection - | TextEditor::TextEditorActionHandler::UnCollapseAll - | TextEditor::TextEditorActionHandler::FollowSymbolUnderCursor); + setEditorActionHandlers(TextEditorActionHandler::Format + | TextEditorActionHandler::UnCommentSelection + | TextEditorActionHandler::UnCollapseAll + | TextEditorActionHandler::FollowSymbolUnderCursor); setDocumentCreator([]() { return new PythonDocument; }); - setEditorWidgetCreator(createEditorWidget); + setEditorWidgetCreator([]() { return new PythonEditorWidget; }); setIndenterCreator([](QTextDocument *doc) { return new PythonIndenter(doc); }); setSyntaxHighlighterCreator([] { return new PythonHighlighter; }); setCommentDefinition(CommentDefinition::HashStyle); @@ -134,3 +268,5 @@ PythonEditorFactory::PythonEditorFactory() } } // Python::Internal + +#include "pythoneditor.moc" diff --git a/src/plugins/python/pythonutils.cpp b/src/plugins/python/pythonutils.cpp index 24d8a1f1880..346c42d183d 100644 --- a/src/plugins/python/pythonutils.cpp +++ b/src/plugins/python/pythonutils.cpp @@ -22,6 +22,12 @@ using namespace Utils; namespace Python::Internal { +static QHash &userDefinedPythonsForDocument() +{ + static QHash userDefines; + return userDefines; +} + FilePath detectPython(const FilePath &documentPath) { Project *project = documentPath.isEmpty() ? nullptr @@ -42,6 +48,10 @@ FilePath detectPython(const FilePath &documentPath) } } + const FilePath userDefined = userDefinedPythonsForDocument().value(documentPath); + if (userDefined.exists()) + return userDefined; + // check whether this file is inside a python virtual environment const QList venvInterpreters = PythonSettings::detectPythonVenvs(documentPath); if (!venvInterpreters.isEmpty()) @@ -71,6 +81,11 @@ FilePath detectPython(const FilePath &documentPath) return PythonSettings::interpreters().value(0).command; } +void definePythonForDocument(const FilePath &documentPath, const FilePath &python) +{ + userDefinedPythonsForDocument()[documentPath] = python; +} + static QStringList replImportArgs(const FilePath &pythonFile, ReplType type) { using MimeTypes = QList; diff --git a/src/plugins/python/pythonutils.h b/src/plugins/python/pythonutils.h index c8adb308ed1..8d5b06974ef 100644 --- a/src/plugins/python/pythonutils.h +++ b/src/plugins/python/pythonutils.h @@ -10,6 +10,7 @@ namespace Python::Internal { enum class ReplType { Unmodified, Import, ImportToplevel }; void openPythonRepl(QObject *parent, const Utils::FilePath &file, ReplType type); Utils::FilePath detectPython(const Utils::FilePath &documentPath); +void definePythonForDocument(const Utils::FilePath &documentPath, const Utils::FilePath &python); QString pythonName(const Utils::FilePath &pythonPath); class PythonProject;