Files
qt-creator/src/plugins/python/pythonsettings.cpp

966 lines
34 KiB
C++
Raw Normal View History

/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "pythonsettings.h"
#include "pythonconstants.h"
#include "pythonplugin.h"
#include "pythontr.h"
#include <coreplugin/dialogs/ioptionspage.h>
#include <coreplugin/icore.h>
#include <extensionsystem/pluginmanager.h>
#include <languageclient/languageclient_global.h>
#include <languageclient/languageclientsettings.h>
#include <languageclient/languageclientmanager.h>
#include <texteditor/textdocument.h>
#include <texteditor/texteditor.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <utils/detailswidget.h>
#include <utils/environment.h>
#include <utils/listmodel.h>
#include <utils/layoutbuilder.h>
#include <utils/pathchooser.h>
#include <utils/qtcprocess.h>
#include <utils/treemodel.h>
#include <utils/utilsicons.h>
#include <QDir>
#include <QLabel>
#include <QPushButton>
#include <QPointer>
#include <QSettings>
#include <QStackedWidget>
#include <QTreeView>
#include <QWidget>
#include <QVBoxLayout>
#include <QGroupBox>
#include <QCheckBox>
#include <QJsonDocument>
#include <QJsonObject>
using namespace ProjectExplorer;
using namespace Utils;
using namespace Layouting;
namespace Python::Internal {
static Interpreter createInterpreter(const FilePath &python,
const QString &defaultName,
bool windowedSuffix = false)
{
Interpreter result;
result.id = QUuid::createUuid().toString();
result.command = python;
QtcProcess pythonProcess;
pythonProcess.setProcessChannelMode(QProcess::MergedChannels);
pythonProcess.setTimeoutS(1);
pythonProcess.setCommand({python, {"--version"}});
pythonProcess.runBlocking();
if (pythonProcess.result() == ProcessResult::FinishedWithSuccess)
result.name = pythonProcess.cleanedStdOut().trimmed();
if (result.name.isEmpty())
result.name = defaultName;
if (windowedSuffix)
result.name += " (Windowed)";
QDir pythonDir(python.parentDir().toString());
if (pythonDir.exists() && pythonDir.exists("activate") && pythonDir.cdUp())
result.name += QString(" (%1 Virtual Environment)").arg(pythonDir.dirName());
return result;
}
class InterpreterDetailsWidget : public QWidget
{
Q_OBJECT
public:
InterpreterDetailsWidget()
: m_name(new QLineEdit)
, m_executable(new PathChooser())
{
m_executable->setExpectedKind(PathChooser::ExistingCommand);
connect(m_name, &QLineEdit::textChanged, this, &InterpreterDetailsWidget::changed);
connect(m_executable, &PathChooser::filePathChanged, this, &InterpreterDetailsWidget::changed);
Form {
Tr::tr("Name:"), m_name, br,
Tr::tr("Executable"), m_executable
}.attachTo(this, WithoutMargins);
}
void updateInterpreter(const Interpreter &interpreter)
{
QSignalBlocker blocker(this); // do not emit changed when we change the controls here
m_currentInterpreter = interpreter;
m_name->setText(interpreter.name);
m_executable->setFilePath(interpreter.command);
}
Interpreter toInterpreter()
{
m_currentInterpreter.command = m_executable->filePath();
m_currentInterpreter.name = m_name->text();
return m_currentInterpreter;
}
QLineEdit *m_name = nullptr;
PathChooser *m_executable = nullptr;
Interpreter m_currentInterpreter;
signals:
void changed();
};
class InterpreterOptionsWidget : public QWidget
{
public:
InterpreterOptionsWidget(const QList<Interpreter> &interpreters,
const QString &defaultInterpreter);
void apply();
private:
QTreeView m_view;
ListModel<Interpreter> m_model;
InterpreterDetailsWidget *m_detailsWidget = nullptr;
QPushButton *m_deleteButton = nullptr;
QPushButton *m_makeDefaultButton = nullptr;
QPushButton *m_cleanButton = nullptr;
QString m_defaultId;
void currentChanged(const QModelIndex &index, const QModelIndex &previous);
void detailsChanged();
void updateCleanButton();
void addItem();
void deleteItem();
void makeDefault();
void cleanUp();
};
InterpreterOptionsWidget::InterpreterOptionsWidget(const QList<Interpreter> &interpreters, const QString &defaultInterpreter)
: m_detailsWidget(new InterpreterDetailsWidget())
, m_defaultId(defaultInterpreter)
{
m_model.setDataAccessor([this](const Interpreter &interpreter, int column, int role) -> QVariant {
switch (role) {
case Qt::DisplayRole:
return interpreter.name;
case Qt::FontRole: {
QFont f = font();
f.setBold(interpreter.id == m_defaultId);
return f;
}
case Qt::ToolTipRole:
if (interpreter.command.isEmpty())
return Tr::tr("Executable is empty.");
if (!interpreter.command.exists())
return Tr::tr("%1 does not exist.").arg(interpreter.command.toUserOutput());
if (!interpreter.command.isExecutableFile())
return Tr::tr("%1 is not an executable file.").arg(interpreter.command.toUserOutput());
break;
case Qt::DecorationRole:
if (column == 0 && !interpreter.command.isExecutableFile())
return Utils::Icons::CRITICAL.icon();
break;
default:
break;
}
return {};
});
m_model.setAllData(interpreters);
m_view.setModel(&m_model);
m_view.setHeaderHidden(true);
m_view.setSelectionMode(QAbstractItemView::SingleSelection);
m_view.setSelectionBehavior(QAbstractItemView::SelectItems);
auto addButton = new QPushButton(Tr::tr("&Add"));
m_deleteButton = new QPushButton(Tr::tr("&Delete"));
m_deleteButton->setEnabled(false);
m_makeDefaultButton = new QPushButton(Tr::tr("&Make Default"));
m_makeDefaultButton->setEnabled(false);
m_cleanButton = new QPushButton(Tr::tr("&Clean Up"));
m_cleanButton->setToolTip(Tr::tr("Remove all Python interpreters without a valid executable."));
updateCleanButton();
m_detailsWidget->hide();
Column buttons {
addButton,
m_deleteButton,
m_makeDefaultButton,
m_cleanButton,
st
};
Column {
Row { &m_view, buttons },
m_detailsWidget
}.attachTo(this);
connect(addButton, &QPushButton::pressed, this, &InterpreterOptionsWidget::addItem);
connect(m_deleteButton, &QPushButton::pressed, this, &InterpreterOptionsWidget::deleteItem);
connect(m_makeDefaultButton, &QPushButton::pressed, this, &InterpreterOptionsWidget::makeDefault);
connect(m_cleanButton, &QPushButton::pressed, this, &InterpreterOptionsWidget::cleanUp);
connect(m_detailsWidget, &InterpreterDetailsWidget::changed,
this, &InterpreterOptionsWidget::detailsChanged);
connect(m_view.selectionModel(), &QItemSelectionModel::currentChanged,
this, &InterpreterOptionsWidget::currentChanged);
}
void InterpreterOptionsWidget::apply()
{
QList<Interpreter> interpreters;
for (const TreeItem *treeItem : m_model)
interpreters << static_cast<const ListItem<Interpreter> *>(treeItem)->itemData;
PythonSettings::setInterpreter(interpreters, m_defaultId);
}
void InterpreterOptionsWidget::currentChanged(const QModelIndex &index, const QModelIndex &previous)
{
if (previous.isValid()) {
m_model.itemAt(previous.row())->itemData = m_detailsWidget->toInterpreter();
emit m_model.dataChanged(previous, previous);
}
if (index.isValid()) {
m_detailsWidget->updateInterpreter(m_model.itemAt(index.row())->itemData);
m_detailsWidget->show();
} else {
m_detailsWidget->hide();
}
m_deleteButton->setEnabled(index.isValid());
m_makeDefaultButton->setEnabled(index.isValid());
}
void InterpreterOptionsWidget::detailsChanged()
{
const QModelIndex &index = m_view.currentIndex();
if (index.isValid()) {
m_model.itemAt(index.row())->itemData = m_detailsWidget->toInterpreter();
emit m_model.dataChanged(index, index);
}
updateCleanButton();
}
void InterpreterOptionsWidget::updateCleanButton()
{
m_cleanButton->setEnabled(Utils::anyOf(m_model.allData(), [](const Interpreter &interpreter) {
return !interpreter.command.isExecutableFile();
}));
}
void InterpreterOptionsWidget::addItem()
{
const QModelIndex &index = m_model.indexForItem(
m_model.appendItem({QUuid::createUuid().toString(), QString("Python"), FilePath(), false}));
QTC_ASSERT(index.isValid(), return);
m_view.setCurrentIndex(index);
updateCleanButton();
}
void InterpreterOptionsWidget::deleteItem()
{
const QModelIndex &index = m_view.currentIndex();
if (index.isValid())
m_model.destroyItem(m_model.itemAt(index.row()));
updateCleanButton();
}
class InterpreterOptionsPage : public Core::IOptionsPage
{
Q_OBJECT
public:
InterpreterOptionsPage();
void setInterpreter(const QList<Interpreter> &interpreters) { m_interpreters = interpreters; }
void addInterpreter(const Interpreter &interpreter) { m_interpreters << interpreter; }
QList<Interpreter> interpreters() const { return m_interpreters; }
void setDefaultInterpreter(const QString &defaultId)
{ m_defaultInterpreterId = defaultId; }
Interpreter defaultInterpreter() const;
QWidget *widget() override;
void apply() override;
void finish() override;
private:
QPointer<InterpreterOptionsWidget> m_widget;
QList<Interpreter> m_interpreters;
QString m_defaultInterpreterId;
};
InterpreterOptionsPage::InterpreterOptionsPage()
{
setId(Constants::C_PYTHONOPTIONS_PAGE_ID);
setDisplayName(Tr::tr("Interpreters"));
setCategory(Constants::C_PYTHON_SETTINGS_CATEGORY);
setDisplayCategory(Tr::tr("Python"));
setCategoryIconPath(":/python/images/settingscategory_python.png");
}
Interpreter InterpreterOptionsPage::defaultInterpreter() const
{
if (m_defaultInterpreterId.isEmpty())
return {};
return Utils::findOrDefault(m_interpreters, [this](const Interpreter &interpreter) {
return interpreter.id == m_defaultInterpreterId;
});
}
QWidget *InterpreterOptionsPage::widget()
{
if (!m_widget)
m_widget = new InterpreterOptionsWidget(m_interpreters, m_defaultInterpreterId);
return m_widget;
}
void InterpreterOptionsPage::apply()
{
if (m_widget)
m_widget->apply();
}
void InterpreterOptionsPage::finish()
{
delete m_widget;
m_widget = nullptr;
}
static bool alreadyRegistered(const QList<Interpreter> &pythons, const FilePath &pythonExecutable)
{
return Utils::anyOf(pythons, [pythonExecutable](const Interpreter &interpreter) {
return interpreter.command.toFileInfo().canonicalFilePath()
== pythonExecutable.toFileInfo().canonicalFilePath();
});
}
static InterpreterOptionsPage &interpreterOptionsPage()
{
static InterpreterOptionsPage page;
return page;
}
static const QStringList &plugins()
{
static const QStringList plugins{"flake8",
"jedi_completion",
"jedi_definition",
"jedi_hover",
"jedi_references",
"jedi_signature_help",
"jedi_symbols",
"mccabe",
"pycodestyle",
"pydocstyle",
"pyflakes",
"pylint",
"rope_completion",
"yapf"};
return plugins;
}
class PyLSConfigureWidget : public QWidget
{
public:
PyLSConfigureWidget()
: m_editor(LanguageClient::jsonEditor())
, m_advancedLabel(new QLabel)
, m_pluginsGroup(new QGroupBox(Tr::tr("Plugins:")))
, m_mainGroup(new QGroupBox(Tr::tr("Use Python Language Server")))
{
m_mainGroup->setCheckable(true);
auto mainGroupLayout = new QVBoxLayout;
auto pluginsLayout = new QVBoxLayout;
m_pluginsGroup->setLayout(pluginsLayout);
m_pluginsGroup->setFlat(true);
for (const QString &plugin : plugins()) {
auto checkBox = new QCheckBox(plugin, this);
connect(checkBox, &QCheckBox::clicked, this, [this, plugin, checkBox]() {
updatePluginEnabled(checkBox->checkState(), plugin);
});
m_checkBoxes[plugin] = checkBox;
pluginsLayout->addWidget(checkBox);
}
mainGroupLayout->addWidget(m_pluginsGroup);
const QString labelText = Tr::tr(
"For a complete list of available options, consult the <a "
"href=\"https://github.com/python-lsp/python-lsp-server/blob/develop/"
"CONFIGURATION.md\">Python LSP Server configuration documentation</a>.");
m_advancedLabel->setText(labelText);
m_advancedLabel->setOpenExternalLinks(true);
mainGroupLayout->addWidget(m_advancedLabel);
mainGroupLayout->addWidget(m_editor->editorWidget(), 1);
setAdvanced(false);
mainGroupLayout->addStretch();
auto advanced = new QCheckBox(Tr::tr("Advanced"));
advanced->setChecked(false);
connect(advanced,
&QCheckBox::toggled,
this,
&PyLSConfigureWidget::setAdvanced);
mainGroupLayout->addWidget(advanced);
m_mainGroup->setLayout(mainGroupLayout);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(m_mainGroup);
setLayout(mainLayout);
}
void initialize(bool enabled, const QString &configuration)
{
m_editor->textDocument()->setPlainText(configuration);
m_mainGroup->setChecked(enabled);
updateCheckboxes();
}
void apply()
{
PythonSettings::setPylsEnabled(m_mainGroup->isChecked());
PythonSettings::setPyLSConfiguration(m_editor->textDocument()->plainText());
}
private:
void setAdvanced(bool advanced)
{
m_editor->editorWidget()->setVisible(advanced);
m_advancedLabel->setVisible(advanced);
m_pluginsGroup->setVisible(!advanced);
updateCheckboxes();
}
void updateCheckboxes()
{
const QJsonDocument document = QJsonDocument::fromJson(
m_editor->textDocument()->plainText().toUtf8());
if (document.isObject()) {
const QJsonObject pluginsObject
= document.object()["pylsp"].toObject()["plugins"].toObject();
for (const QString &plugin : plugins()) {
auto checkBox = m_checkBoxes[plugin];
if (!checkBox)
continue;
const QJsonValue enabled = pluginsObject[plugin].toObject()["enabled"];
if (!enabled.isBool())
checkBox->setCheckState(Qt::PartiallyChecked);
else
checkBox->setCheckState(enabled.toBool(false) ? Qt::Checked : Qt::Unchecked);
}
}
}
void updatePluginEnabled(Qt::CheckState check, const QString &plugin)
{
if (check == Qt::PartiallyChecked)
return;
QJsonDocument document = QJsonDocument::fromJson(
m_editor->textDocument()->plainText().toUtf8());
QJsonObject config;
if (!document.isNull())
config = document.object();
QJsonObject pylsp = config["pylsp"].toObject();
QJsonObject plugins = pylsp["plugins"].toObject();
QJsonObject pluginValue = plugins[plugin].toObject();
pluginValue.insert("enabled", check == Qt::Checked);
plugins.insert(plugin, pluginValue);
pylsp.insert("plugins", plugins);
config.insert("pylsp", pylsp);
document.setObject(config);
m_editor->textDocument()->setPlainText(QString::fromUtf8(document.toJson()));
}
QMap<QString, QCheckBox *> m_checkBoxes;
TextEditor::BaseTextEditor *m_editor = nullptr;
QLabel *m_advancedLabel = nullptr;
QGroupBox *m_pluginsGroup = nullptr;
QGroupBox *m_mainGroup = nullptr;
};
class PyLSOptionsPage : public Core::IOptionsPage
{
public:
PyLSOptionsPage();
bool enabled() const { return m_enabled; }
void setEnabled(bool enabled);
void setConfiguration(const QString &configuration) { m_configuration = configuration; }
QString configuration() const { return m_configuration; }
QWidget *widget() override;
void apply() override;
void finish() override;
private:
QPointer<PyLSConfigureWidget> m_widget;
bool m_enabled = true;
QString m_configuration;
};
PyLSOptionsPage::PyLSOptionsPage()
{
setId(Constants::C_PYLSCONFIGURATION_PAGE_ID);
setDisplayName(Tr::tr("Language Server Configuration"));
setCategory(Constants::C_PYTHON_SETTINGS_CATEGORY);
}
QWidget *PyLSOptionsPage::widget()
{
if (!m_widget) {
m_widget = new PyLSConfigureWidget();
m_widget->initialize(m_enabled, m_configuration);
}
return m_widget;
}
void PyLSOptionsPage::apply()
{
if (m_widget)
m_widget->apply();
}
void PyLSOptionsPage::finish()
{
delete m_widget;
m_widget = nullptr;
}
void PyLSOptionsPage::setEnabled(bool enabled)
{
m_enabled = enabled;
}
static PyLSOptionsPage &pylspOptionsPage()
{
static PyLSOptionsPage page;
return page;
}
void InterpreterOptionsWidget::makeDefault()
{
const QModelIndex &index = m_view.currentIndex();
if (index.isValid()) {
QModelIndex defaultIndex = m_model.findIndex([this](const Interpreter &interpreter) {
return interpreter.id == m_defaultId;
});
m_defaultId = m_model.itemAt(index.row())->itemData.id;
emit m_model.dataChanged(index, index, {Qt::FontRole});
if (defaultIndex.isValid())
emit m_model.dataChanged(defaultIndex, defaultIndex, {Qt::FontRole});
}
}
void InterpreterOptionsWidget::cleanUp()
{
m_model.destroyItems(
[](const Interpreter &interpreter) { return !interpreter.command.isExecutableFile(); });
updateCleanButton();
}
constexpr char settingsGroupKey[] = "Python";
constexpr char interpreterKey[] = "Interpeter";
constexpr char defaultKey[] = "DefaultInterpeter";
constexpr char pylsEnabledKey[] = "PylsEnabled";
constexpr char pylsConfigurationKey[] = "PylsConfiguration";
struct SavedSettings
{
QList<Interpreter> pythons;
QString defaultId;
QString pylsConfiguration;
bool pylsEnabled = true;
};
static QString defaultPylsConfiguration()
{
static QJsonObject configuration;
if (configuration.isEmpty()) {
QJsonObject enabled;
enabled.insert("enabled", true);
QJsonObject disabled;
disabled.insert("enabled", false);
QJsonObject plugins;
plugins.insert("flake8", disabled);
plugins.insert("jedi_completion", enabled);
plugins.insert("jedi_definition", enabled);
plugins.insert("jedi_hover", enabled);
plugins.insert("jedi_references", enabled);
plugins.insert("jedi_signature_help", enabled);
plugins.insert("jedi_symbols", enabled);
plugins.insert("mccabe", disabled);
plugins.insert("pycodestyle", disabled);
plugins.insert("pydocstyle", disabled);
plugins.insert("pyflakes", enabled);
plugins.insert("pylint", disabled);
plugins.insert("rope_completion", enabled);
plugins.insert("yapf", enabled);
QJsonObject pylsp;
pylsp.insert("plugins", plugins);
configuration.insert("pylsp", pylsp);
}
return QString::fromUtf8(QJsonDocument(configuration).toJson());
}
static void disableOutdatedPylsNow()
{
using namespace LanguageClient;
const QList<BaseSettings *>
settings = LanguageClientSettings::pageSettings();
for (const BaseSettings *setting : settings) {
if (setting->m_settingsTypeId != LanguageClient::Constants::LANGUAGECLIENT_STDIO_SETTINGS_ID)
continue;
auto stdioSetting = static_cast<const StdIOSettings *>(setting);
if (stdioSetting->arguments().startsWith("-m pyls")
&& stdioSetting->m_languageFilter.isSupported("foo.py", Constants::C_PY_MIMETYPE)) {
LanguageClientManager::enableClientSettings(stdioSetting->m_id, false);
}
}
}
static void disableOutdatedPyls()
{
using namespace ExtensionSystem;
if (PluginManager::isInitializationDone()) {
disableOutdatedPylsNow();
} else {
QObject::connect(PluginManager::instance(), &PluginManager::initializationDone,
PythonPlugin::instance(), &disableOutdatedPylsNow);
}
}
static SavedSettings fromSettings(QSettings *settings)
{
SavedSettings result;
settings->beginGroup(settingsGroupKey);
const QVariantList interpreters = settings->value(interpreterKey).toList();
QList<Interpreter> oldSettings;
for (const QVariant &interpreterVar : interpreters) {
auto interpreterList = interpreterVar.toList();
const Interpreter interpreter{interpreterList.value(0).toString(),
interpreterList.value(1).toString(),
FilePath::fromVariant(interpreterList.value(2)),
interpreterList.value(3, true).toBool()};
if (interpreterList.size() == 3)
oldSettings << interpreter;
else if (interpreterList.size() == 4)
result.pythons << interpreter;
}
for (const Interpreter &interpreter : qAsConst(oldSettings)) {
if (Utils::anyOf(result.pythons, Utils::equal(&Interpreter::id, interpreter.id)))
continue;
result.pythons << interpreter;
}
result.pythons = Utils::filtered(result.pythons, [](const Interpreter &interpreter){
return !interpreter.autoDetected || interpreter.command.isExecutableFile();
});
result.defaultId = settings->value(defaultKey).toString();
QVariant pylsEnabled = settings->value(pylsEnabledKey);
if (pylsEnabled.isNull())
disableOutdatedPyls();
else
result.pylsEnabled = pylsEnabled.toBool();
const QVariant pylsConfiguration = settings->value(pylsConfigurationKey);
if (!pylsConfiguration.isNull())
result.pylsConfiguration = pylsConfiguration.toString();
else
result.pylsConfiguration = defaultPylsConfiguration();
settings->endGroup();
return result;
}
static void toSettings(QSettings *settings, const SavedSettings &savedSettings)
{
settings->beginGroup(settingsGroupKey);
QVariantList interpretersVar;
for (const Interpreter &interpreter : savedSettings.pythons) {
QVariantList interpreterVar{interpreter.id,
interpreter.name,
interpreter.command.toVariant()};
interpretersVar.append(QVariant(interpreterVar)); // old settings
interpreterVar.append(interpreter.autoDetected);
interpretersVar.append(QVariant(interpreterVar)); // new settings
}
settings->setValue(interpreterKey, interpretersVar);
settings->setValue(defaultKey, savedSettings.defaultId);
settings->setValue(pylsConfigurationKey, savedSettings.pylsConfiguration);
settings->setValue(pylsEnabledKey, savedSettings.pylsEnabled);
settings->endGroup();
}
static void addPythonsFromRegistry(QList<Interpreter> &pythons)
{
QSettings pythonRegistry("HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore",
QSettings::NativeFormat);
for (const QString &versionGroup : pythonRegistry.childGroups()) {
pythonRegistry.beginGroup(versionGroup);
QString name = pythonRegistry.value("DisplayName").toString();
QVariant regVal = pythonRegistry.value("InstallPath/ExecutablePath");
if (regVal.isValid()) {
const FilePath &executable = FilePath::fromUserInput(regVal.toString());
if (executable.exists() && !alreadyRegistered(pythons, executable)) {
pythons << Interpreter{QUuid::createUuid().toString(),
name,
FilePath::fromUserInput(regVal.toString())};
}
}
regVal = pythonRegistry.value("InstallPath/WindowedExecutablePath");
if (regVal.isValid()) {
const FilePath &executable = FilePath::fromUserInput(regVal.toString());
if (executable.exists() && !alreadyRegistered(pythons, executable)) {
pythons << Interpreter{QUuid::createUuid().toString(),
name + Tr::tr(" (Windowed)"),
FilePath::fromUserInput(regVal.toString())};
}
}
regVal = pythonRegistry.value("InstallPath/.");
if (regVal.isValid()) {
const FilePath &path = FilePath::fromUserInput(regVal.toString());
const FilePath python = path.pathAppended("python").withExecutableSuffix();
if (python.exists() && !alreadyRegistered(pythons, python))
pythons << createInterpreter(python, "Python " + versionGroup);
const FilePath pythonw = path.pathAppended("pythonw").withExecutableSuffix();
if (pythonw.exists() && !alreadyRegistered(pythons, pythonw))
pythons << createInterpreter(pythonw, "Python " + versionGroup, true);
}
pythonRegistry.endGroup();
}
}
static void addPythonsFromPath(QList<Interpreter> &pythons)
{
const auto &env = Environment::systemEnvironment();
if (HostOsInfo::isWindowsHost()) {
for (const FilePath &executable : env.findAllInPath("python")) {
// Windows creates empty redirector files that may interfere
if (executable.toFileInfo().size() == 0)
continue;
if (executable.exists() && !alreadyRegistered(pythons, executable))
pythons << createInterpreter(executable, "Python from Path");
}
for (const FilePath &executable : env.findAllInPath("pythonw")) {
if (executable.exists() && !alreadyRegistered(pythons, executable))
pythons << createInterpreter(executable, "Python from Path", true);
}
} else {
const QStringList filters = {"python",
"python[1-9].[0-9]",
"python[1-9].[1-9][0-9]",
"python[1-9]"};
for (const FilePath &path : env.path()) {
const QDir dir(path.toString());
for (const QFileInfo &fi : dir.entryInfoList(filters)) {
const FilePath executable = Utils::FilePath::fromFileInfo(fi);
if (executable.exists() && !alreadyRegistered(pythons, executable))
pythons << createInterpreter(executable, "Python from Path");
}
}
}
}
static QString idForPythonFromPath(QList<Interpreter> pythons)
{
FilePath pythonFromPath = Environment::systemEnvironment().searchInPath("python3");
if (pythonFromPath.isEmpty())
pythonFromPath = Environment::systemEnvironment().searchInPath("python");
if (pythonFromPath.isEmpty())
return {};
const Interpreter &defaultInterpreter
= findOrDefault(pythons, [pythonFromPath](const Interpreter &interpreter) {
return interpreter.command == pythonFromPath;
});
return defaultInterpreter.id;
}
static PythonSettings *settingsInstance = nullptr;
PythonSettings::PythonSettings() = default;
void PythonSettings::init()
{
QTC_ASSERT(!settingsInstance, return );
settingsInstance = new PythonSettings();
const SavedSettings &settings = fromSettings(Core::ICore::settings());
pylspOptionsPage().setConfiguration(settings.pylsConfiguration);
pylspOptionsPage().setEnabled(settings.pylsEnabled);
QList<Interpreter> pythons = settings.pythons;
if (HostOsInfo::isWindowsHost())
addPythonsFromRegistry(pythons);
addPythonsFromPath(pythons);
const QString &defaultId = !settings.defaultId.isEmpty() ? settings.defaultId
: idForPythonFromPath(pythons);
setInterpreter(pythons, defaultId);
}
void PythonSettings::setInterpreter(const QList<Interpreter> &interpreters, const QString &defaultId)
{
if (defaultId == interpreterOptionsPage().defaultInterpreter().id
&& interpreters == interpreterOptionsPage().interpreters()) {
return;
}
interpreterOptionsPage().setInterpreter(interpreters);
interpreterOptionsPage().setDefaultInterpreter(defaultId);
saveSettings();
}
void PythonSettings::setPyLSConfiguration(const QString &configuration)
{
if (configuration == pylspOptionsPage().configuration())
return;
pylspOptionsPage().setConfiguration(configuration);
saveSettings();
emit instance()->pylsConfigurationChanged(configuration);
}
void PythonSettings::setPylsEnabled(const bool &enabled)
{
if (enabled == pylspOptionsPage().enabled())
return;
pylspOptionsPage().setEnabled(enabled);
saveSettings();
emit instance()->pylsEnabledChanged(enabled);
}
bool PythonSettings::pylsEnabled()
{
return pylspOptionsPage().enabled();
}
QString PythonSettings::pyLSConfiguration()
{
return pylspOptionsPage().configuration();
}
void PythonSettings::addInterpreter(const Interpreter &interpreter, bool isDefault)
{
interpreterOptionsPage().addInterpreter(interpreter);
if (isDefault)
interpreterOptionsPage().setDefaultInterpreter(interpreter.id);
saveSettings();
}
PythonSettings *PythonSettings::instance()
{
QTC_CHECK(settingsInstance);
return settingsInstance;
}
QList<Interpreter> PythonSettings::detectPythonVenvs(const FilePath &path)
{
QList<Interpreter> result;
QDir dir = path.toFileInfo().isDir() ? QDir(path.toString()) : path.toFileInfo().dir();
if (dir.exists()) {
const QString venvPython = HostOsInfo::withExecutableSuffix("python");
const QString activatePath = HostOsInfo::isWindowsHost() ? QString{"Scripts"}
: QString{"bin"};
do {
for (const QString &directory : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
if (dir.cd(directory)) {
if (dir.cd(activatePath)) {
if (dir.exists("activate") && dir.exists(venvPython)) {
FilePath python = FilePath::fromString(dir.absoluteFilePath(venvPython));
dir.cdUp();
const QString defaultName = QString("Python (%1 Virtual Environment)")
.arg(dir.dirName());
Interpreter interpreter
= Utils::findOrDefault(PythonSettings::interpreters(),
Utils::equal(&Interpreter::command, python));
if (interpreter.command.isEmpty()) {
interpreter = createInterpreter(python, defaultName);
PythonSettings::addInterpreter(interpreter);
}
result << interpreter;
} else {
dir.cdUp();
}
}
dir.cdUp();
}
}
} while (dir.cdUp() && !(dir.isRoot() && Utils::HostOsInfo::isAnyUnixHost()));
}
return result;
}
void PythonSettings::saveSettings()
{
const QList<Interpreter> &interpreters = interpreterOptionsPage().interpreters();
const QString defaultId = interpreterOptionsPage().defaultInterpreter().id;
const QString pylsConfiguration = pylspOptionsPage().configuration();
const bool pylsEnabled = pylspOptionsPage().enabled();
toSettings(Core::ICore::settings(), {interpreters, defaultId, pylsConfiguration, pylsEnabled});
if (QTC_GUARD(settingsInstance))
emit settingsInstance->interpretersChanged(interpreters, defaultId);
}
QList<Interpreter> PythonSettings::interpreters()
{
return interpreterOptionsPage().interpreters();
}
Interpreter PythonSettings::defaultInterpreter()
{
return interpreterOptionsPage().defaultInterpreter();
}
Interpreter PythonSettings::interpreter(const QString &interpreterId)
{
const QList<Interpreter> interpreters = PythonSettings::interpreters();
return Utils::findOrDefault(interpreters, Utils::equal(&Interpreter::id, interpreterId));
}
} // Python::Internal
#include "pythonsettings.moc"