forked from qt-creator/qt-creator
Python: add interpreter selector to editor toolbar
Fixes: PYSIDE-2154 Change-Id: If5e90f5bf2923b61af37ebbfcd35c512b3b07db4 Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
@@ -806,7 +806,14 @@ void InterpreterAspect::setDefaultInterpreter(const Interpreter &interpreter)
|
|||||||
|
|
||||||
void InterpreterAspect::setCurrentInterpreter(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();
|
emit changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -853,13 +860,12 @@ void InterpreterAspect::updateComboBox()
|
|||||||
{
|
{
|
||||||
int currentIndex = -1;
|
int currentIndex = -1;
|
||||||
int defaultIndex = -1;
|
int defaultIndex = -1;
|
||||||
const QString currentId = m_currentId;
|
|
||||||
m_comboBox->clear();
|
m_comboBox->clear();
|
||||||
for (const Interpreter &interpreter : std::as_const(m_interpreters)) {
|
for (const Interpreter &interpreter : std::as_const(m_interpreters)) {
|
||||||
int index = m_comboBox->count();
|
int index = m_comboBox->count();
|
||||||
m_comboBox->addItem(interpreter.name);
|
m_comboBox->addItem(interpreter.name);
|
||||||
m_comboBox->setItemData(index, interpreter.command.toUserOutput(), Qt::ToolTipRole);
|
m_comboBox->setItemData(index, interpreter.command.toUserOutput(), Qt::ToolTipRole);
|
||||||
if (interpreter.id == currentId)
|
if (interpreter.id == m_currentId)
|
||||||
currentIndex = index;
|
currentIndex = index;
|
||||||
if (interpreter.id == m_defaultId)
|
if (interpreter.id == m_defaultId)
|
||||||
defaultIndex = index;
|
defaultIndex = index;
|
||||||
|
@@ -4,9 +4,7 @@
|
|||||||
#include "pyside.h"
|
#include "pyside.h"
|
||||||
|
|
||||||
#include "pipsupport.h"
|
#include "pipsupport.h"
|
||||||
#include "pythonconstants.h"
|
|
||||||
#include "pythonplugin.h"
|
#include "pythonplugin.h"
|
||||||
#include "pythonproject.h"
|
|
||||||
#include "pythonsettings.h"
|
#include "pythonsettings.h"
|
||||||
#include "pythontr.h"
|
#include "pythontr.h"
|
||||||
#include "pythonutils.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.")
|
const QString installTooltip = Tr::tr("Install %1 for %2 using pip package installer.")
|
||||||
.arg(pySide, python.toUserOutput());
|
.arg(pySide, python.toUserOutput());
|
||||||
info.addCustomButton(Tr::tr("Install"), installCallback, installTooltip);
|
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<InfoBarEntry::ComboInfo> interpreters = Utils::transform(
|
|
||||||
PythonSettings::interpreters(), [](const Interpreter &interpreter) {
|
|
||||||
return InfoBarEntry::ComboInfo{interpreter.name, interpreter.id};
|
|
||||||
});
|
|
||||||
auto interpreterChangeCallback =
|
|
||||||
[=, rc = QPointer<RunConfiguration>(runConfiguration)](
|
|
||||||
const InfoBarEntry::ComboInfo &info) {
|
|
||||||
changeInterpreter(info.data.toString(), rc);
|
|
||||||
};
|
|
||||||
|
|
||||||
auto interpreterAspect = runConfiguration->aspect<InterpreterAspect>();
|
|
||||||
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);
|
document->infoBar()->addInfo(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -14,13 +14,23 @@
|
|||||||
|
|
||||||
#include <coreplugin/actionmanager/actionmanager.h>
|
#include <coreplugin/actionmanager/actionmanager.h>
|
||||||
#include <coreplugin/actionmanager/commandbutton.h>
|
#include <coreplugin/actionmanager/commandbutton.h>
|
||||||
|
#include <coreplugin/icore.h>
|
||||||
|
|
||||||
|
#include <projectexplorer/project.h>
|
||||||
|
#include <projectexplorer/projectexplorer.h>
|
||||||
|
#include <projectexplorer/session.h>
|
||||||
|
#include <projectexplorer/target.h>
|
||||||
|
|
||||||
#include <texteditor/textdocument.h>
|
#include <texteditor/textdocument.h>
|
||||||
#include <texteditor/texteditoractionhandler.h>
|
#include <texteditor/texteditoractionhandler.h>
|
||||||
|
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
|
#include <QActionGroup>
|
||||||
|
#include <QComboBox>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
|
|
||||||
|
using namespace ProjectExplorer;
|
||||||
|
using namespace TextEditor;
|
||||||
using namespace Utils;
|
using namespace Utils;
|
||||||
|
|
||||||
namespace Python::Internal {
|
namespace Python::Internal {
|
||||||
@@ -60,30 +70,11 @@ static void registerReplAction(QObject *parent)
|
|||||||
Constants::PYTHON_OPEN_REPL_IMPORT_TOPLEVEL);
|
Constants::PYTHON_OPEN_REPL_IMPORT_TOPLEVEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static QWidget *createEditorWidget()
|
class PythonDocument : public TextDocument
|
||||||
{
|
|
||||||
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
|
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
PythonDocument() : TextEditor::TextDocument(Constants::C_PYTHONEDITOR_ID)
|
PythonDocument() : TextDocument(Constants::C_PYTHONEDITOR_ID)
|
||||||
{
|
{
|
||||||
connect(PythonSettings::instance(),
|
connect(PythonSettings::instance(),
|
||||||
&PythonSettings::pylsEnabledChanged,
|
&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<QMetaObject::Connection> 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<PythonDocument *>(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<InterpreterAspect>()) {
|
||||||
|
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<InterpreterAspect>()) {
|
||||||
|
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<Interpreter> 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()
|
PythonEditorFactory::PythonEditorFactory()
|
||||||
{
|
{
|
||||||
registerReplAction(this);
|
registerReplAction(this);
|
||||||
@@ -119,13 +253,13 @@ PythonEditorFactory::PythonEditorFactory()
|
|||||||
QCoreApplication::translate("OpenWith::Editors", Constants::C_EDITOR_DISPLAY_NAME));
|
QCoreApplication::translate("OpenWith::Editors", Constants::C_EDITOR_DISPLAY_NAME));
|
||||||
addMimeType(Constants::C_PY_MIMETYPE);
|
addMimeType(Constants::C_PY_MIMETYPE);
|
||||||
|
|
||||||
setEditorActionHandlers(TextEditor::TextEditorActionHandler::Format
|
setEditorActionHandlers(TextEditorActionHandler::Format
|
||||||
| TextEditor::TextEditorActionHandler::UnCommentSelection
|
| TextEditorActionHandler::UnCommentSelection
|
||||||
| TextEditor::TextEditorActionHandler::UnCollapseAll
|
| TextEditorActionHandler::UnCollapseAll
|
||||||
| TextEditor::TextEditorActionHandler::FollowSymbolUnderCursor);
|
| TextEditorActionHandler::FollowSymbolUnderCursor);
|
||||||
|
|
||||||
setDocumentCreator([]() { return new PythonDocument; });
|
setDocumentCreator([]() { return new PythonDocument; });
|
||||||
setEditorWidgetCreator(createEditorWidget);
|
setEditorWidgetCreator([]() { return new PythonEditorWidget; });
|
||||||
setIndenterCreator([](QTextDocument *doc) { return new PythonIndenter(doc); });
|
setIndenterCreator([](QTextDocument *doc) { return new PythonIndenter(doc); });
|
||||||
setSyntaxHighlighterCreator([] { return new PythonHighlighter; });
|
setSyntaxHighlighterCreator([] { return new PythonHighlighter; });
|
||||||
setCommentDefinition(CommentDefinition::HashStyle);
|
setCommentDefinition(CommentDefinition::HashStyle);
|
||||||
@@ -134,3 +268,5 @@ PythonEditorFactory::PythonEditorFactory()
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // Python::Internal
|
} // Python::Internal
|
||||||
|
|
||||||
|
#include "pythoneditor.moc"
|
||||||
|
@@ -22,6 +22,12 @@ using namespace Utils;
|
|||||||
|
|
||||||
namespace Python::Internal {
|
namespace Python::Internal {
|
||||||
|
|
||||||
|
static QHash<FilePath, FilePath> &userDefinedPythonsForDocument()
|
||||||
|
{
|
||||||
|
static QHash<FilePath, FilePath> userDefines;
|
||||||
|
return userDefines;
|
||||||
|
}
|
||||||
|
|
||||||
FilePath detectPython(const FilePath &documentPath)
|
FilePath detectPython(const FilePath &documentPath)
|
||||||
{
|
{
|
||||||
Project *project = documentPath.isEmpty() ? nullptr
|
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
|
// check whether this file is inside a python virtual environment
|
||||||
const QList<Interpreter> venvInterpreters = PythonSettings::detectPythonVenvs(documentPath);
|
const QList<Interpreter> venvInterpreters = PythonSettings::detectPythonVenvs(documentPath);
|
||||||
if (!venvInterpreters.isEmpty())
|
if (!venvInterpreters.isEmpty())
|
||||||
@@ -71,6 +81,11 @@ FilePath detectPython(const FilePath &documentPath)
|
|||||||
return PythonSettings::interpreters().value(0).command;
|
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)
|
static QStringList replImportArgs(const FilePath &pythonFile, ReplType type)
|
||||||
{
|
{
|
||||||
using MimeTypes = QList<MimeType>;
|
using MimeTypes = QList<MimeType>;
|
||||||
|
@@ -10,6 +10,7 @@ namespace Python::Internal {
|
|||||||
enum class ReplType { Unmodified, Import, ImportToplevel };
|
enum class ReplType { Unmodified, Import, ImportToplevel };
|
||||||
void openPythonRepl(QObject *parent, const Utils::FilePath &file, ReplType type);
|
void openPythonRepl(QObject *parent, const Utils::FilePath &file, ReplType type);
|
||||||
Utils::FilePath detectPython(const Utils::FilePath &documentPath);
|
Utils::FilePath detectPython(const Utils::FilePath &documentPath);
|
||||||
|
void definePythonForDocument(const Utils::FilePath &documentPath, const Utils::FilePath &python);
|
||||||
QString pythonName(const Utils::FilePath &pythonPath);
|
QString pythonName(const Utils::FilePath &pythonPath);
|
||||||
|
|
||||||
class PythonProject;
|
class PythonProject;
|
||||||
|
Reference in New Issue
Block a user