2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2019 The Qt Company Ltd.
|
2022-12-21 10:12:09 +01:00
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2019-07-05 09:40:31 +02:00
|
|
|
|
|
|
|
|
#include "pythonsettings.h"
|
|
|
|
|
|
|
|
|
|
#include "pythonconstants.h"
|
2022-06-22 13:46:50 +02:00
|
|
|
#include "pythonplugin.h"
|
2022-07-15 11:49:34 +02:00
|
|
|
#include "pythontr.h"
|
2019-07-05 09:40:31 +02:00
|
|
|
|
|
|
|
|
#include <coreplugin/dialogs/ioptionspage.h>
|
2021-05-05 18:21:22 +02:00
|
|
|
#include <coreplugin/icore.h>
|
2022-07-15 11:49:34 +02:00
|
|
|
|
2022-06-22 13:46:50 +02:00
|
|
|
#include <extensionsystem/pluginmanager.h>
|
2022-07-15 11:49:34 +02:00
|
|
|
|
2022-06-22 13:46:50 +02:00
|
|
|
#include <languageclient/languageclient_global.h>
|
2022-06-07 17:36:24 +02:00
|
|
|
#include <languageclient/languageclientsettings.h>
|
2022-06-22 13:46:50 +02:00
|
|
|
#include <languageclient/languageclientmanager.h>
|
2022-07-15 11:49:34 +02:00
|
|
|
|
2022-06-07 17:36:24 +02:00
|
|
|
#include <texteditor/textdocument.h>
|
|
|
|
|
#include <texteditor/texteditor.h>
|
2022-07-15 11:49:34 +02:00
|
|
|
|
2019-07-05 09:40:31 +02:00
|
|
|
#include <utils/algorithm.h>
|
|
|
|
|
#include <utils/qtcassert.h>
|
|
|
|
|
#include <utils/detailswidget.h>
|
|
|
|
|
#include <utils/environment.h>
|
|
|
|
|
#include <utils/listmodel.h>
|
2021-03-11 18:43:47 +01:00
|
|
|
#include <utils/layoutbuilder.h>
|
2019-07-05 09:40:31 +02:00
|
|
|
#include <utils/pathchooser.h>
|
2021-05-05 18:21:22 +02:00
|
|
|
#include <utils/qtcprocess.h>
|
2019-07-05 09:40:31 +02:00
|
|
|
#include <utils/treemodel.h>
|
2022-03-28 10:07:31 +02:00
|
|
|
#include <utils/utilsicons.h>
|
2019-07-05 09:40:31 +02:00
|
|
|
|
|
|
|
|
#include <QDir>
|
|
|
|
|
#include <QLabel>
|
|
|
|
|
#include <QPushButton>
|
|
|
|
|
#include <QPointer>
|
|
|
|
|
#include <QSettings>
|
|
|
|
|
#include <QStackedWidget>
|
|
|
|
|
#include <QTreeView>
|
|
|
|
|
#include <QWidget>
|
2022-06-07 17:36:24 +02:00
|
|
|
#include <QVBoxLayout>
|
|
|
|
|
#include <QGroupBox>
|
|
|
|
|
#include <QCheckBox>
|
|
|
|
|
#include <QJsonDocument>
|
|
|
|
|
#include <QJsonObject>
|
2019-07-05 09:40:31 +02:00
|
|
|
|
2022-04-13 12:26:54 +02:00
|
|
|
using namespace ProjectExplorer;
|
|
|
|
|
using namespace Utils;
|
|
|
|
|
using namespace Layouting;
|
|
|
|
|
|
2022-07-15 11:49:34 +02:00
|
|
|
namespace Python::Internal {
|
2019-07-05 09:40:31 +02:00
|
|
|
|
2022-04-13 12:26:54 +02:00
|
|
|
static Interpreter createInterpreter(const FilePath &python,
|
|
|
|
|
const QString &defaultName,
|
2022-08-26 15:16:08 +02:00
|
|
|
const QString &suffix = {})
|
2022-04-13 12:26:54 +02:00
|
|
|
{
|
|
|
|
|
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)
|
2022-06-17 14:17:14 +02:00
|
|
|
result.name = pythonProcess.cleanedStdOut().trimmed();
|
2022-04-13 12:26:54 +02:00
|
|
|
if (result.name.isEmpty())
|
|
|
|
|
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());
|
2022-08-26 15:16:08 +02:00
|
|
|
if (!suffix.isEmpty())
|
|
|
|
|
result.name += ' ' + suffix;
|
2022-04-13 12:26:54 +02:00
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2019-07-05 09:40:31 +02:00
|
|
|
|
|
|
|
|
class InterpreterDetailsWidget : public QWidget
|
|
|
|
|
{
|
2022-03-28 10:07:31 +02:00
|
|
|
Q_OBJECT
|
2019-07-05 09:40:31 +02:00
|
|
|
public:
|
2022-11-17 14:58:11 +01:00
|
|
|
InterpreterDetailsWidget(QWidget *parent)
|
|
|
|
|
: QWidget(parent)
|
|
|
|
|
, m_name(new QLineEdit)
|
2022-03-28 10:07:31 +02:00
|
|
|
, m_executable(new PathChooser())
|
2019-07-05 09:40:31 +02:00
|
|
|
{
|
2022-03-28 10:07:31 +02:00
|
|
|
m_executable->setExpectedKind(PathChooser::ExistingCommand);
|
|
|
|
|
|
|
|
|
|
connect(m_name, &QLineEdit::textChanged, this, &InterpreterDetailsWidget::changed);
|
2022-09-02 11:49:36 +02:00
|
|
|
connect(m_executable, &PathChooser::textChanged, this, &InterpreterDetailsWidget::changed);
|
2021-03-11 18:43:47 +01:00
|
|
|
|
|
|
|
|
Form {
|
2022-07-22 18:54:04 +02:00
|
|
|
Tr::tr("Name:"), m_name, br,
|
2022-07-15 11:49:34 +02:00
|
|
|
Tr::tr("Executable"), m_executable
|
2022-07-26 11:35:19 +02:00
|
|
|
}.attachTo(this, WithoutMargins);
|
2019-07-05 09:40:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void updateInterpreter(const Interpreter &interpreter)
|
|
|
|
|
{
|
2022-03-28 10:07:31 +02:00
|
|
|
QSignalBlocker blocker(this); // do not emit changed when we change the controls here
|
|
|
|
|
m_currentInterpreter = interpreter;
|
2019-07-05 09:40:31 +02:00
|
|
|
m_name->setText(interpreter.name);
|
2021-09-08 17:16:43 +02:00
|
|
|
m_executable->setFilePath(interpreter.command);
|
2019-07-05 09:40:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Interpreter toInterpreter()
|
|
|
|
|
{
|
2022-03-28 10:07:31 +02:00
|
|
|
m_currentInterpreter.command = m_executable->filePath();
|
|
|
|
|
m_currentInterpreter.name = m_name->text();
|
|
|
|
|
return m_currentInterpreter;
|
2019-07-05 09:40:31 +02:00
|
|
|
}
|
|
|
|
|
QLineEdit *m_name = nullptr;
|
|
|
|
|
PathChooser *m_executable = nullptr;
|
2022-03-28 10:07:31 +02:00
|
|
|
Interpreter m_currentInterpreter;
|
|
|
|
|
|
|
|
|
|
signals:
|
|
|
|
|
void changed();
|
2019-07-05 09:40:31 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2022-09-02 10:19:45 +02:00
|
|
|
class InterpreterOptionsWidget : public Core::IOptionsPageWidget
|
2019-07-05 09:40:31 +02:00
|
|
|
{
|
|
|
|
|
public:
|
2022-09-02 10:19:45 +02:00
|
|
|
InterpreterOptionsWidget();
|
2019-07-05 09:40:31 +02:00
|
|
|
|
2022-09-02 10:19:45 +02:00
|
|
|
void apply() override;
|
2019-07-05 09:40:31 +02:00
|
|
|
|
2022-08-26 15:16:08 +02:00
|
|
|
void addInterpreter(const Interpreter &interpreter);
|
|
|
|
|
void removeInterpreterFrom(const QString &detectionSource);
|
|
|
|
|
QList<Interpreter> interpreters() const;
|
|
|
|
|
QList<Interpreter> interpreterFrom(const QString &detectionSource) const;
|
|
|
|
|
|
2019-07-05 09:40:31 +02:00
|
|
|
private:
|
2022-11-17 14:58:11 +01:00
|
|
|
QTreeView *m_view = nullptr;
|
2019-07-05 09:40:31 +02:00
|
|
|
ListModel<Interpreter> m_model;
|
|
|
|
|
InterpreterDetailsWidget *m_detailsWidget = nullptr;
|
|
|
|
|
QPushButton *m_deleteButton = nullptr;
|
|
|
|
|
QPushButton *m_makeDefaultButton = nullptr;
|
2022-03-28 13:24:24 +02:00
|
|
|
QPushButton *m_cleanButton = nullptr;
|
2019-07-05 09:40:31 +02:00
|
|
|
QString m_defaultId;
|
|
|
|
|
|
|
|
|
|
void currentChanged(const QModelIndex &index, const QModelIndex &previous);
|
2022-03-28 10:07:31 +02:00
|
|
|
void detailsChanged();
|
2022-03-28 13:24:24 +02:00
|
|
|
void updateCleanButton();
|
2019-07-05 09:40:31 +02:00
|
|
|
void addItem();
|
|
|
|
|
void deleteItem();
|
|
|
|
|
void makeDefault();
|
2022-03-28 13:24:24 +02:00
|
|
|
void cleanUp();
|
2019-07-05 09:40:31 +02:00
|
|
|
};
|
|
|
|
|
|
2022-09-02 10:19:45 +02:00
|
|
|
InterpreterOptionsWidget::InterpreterOptionsWidget()
|
2022-11-17 14:58:11 +01:00
|
|
|
: m_detailsWidget(new InterpreterDetailsWidget(this))
|
2022-09-02 10:19:45 +02:00
|
|
|
, m_defaultId(PythonSettings::defaultInterpreter().id)
|
2019-07-05 09:40:31 +02:00
|
|
|
{
|
2022-03-28 10:07:31 +02:00
|
|
|
m_model.setDataAccessor([this](const Interpreter &interpreter, int column, int role) -> QVariant {
|
|
|
|
|
switch (role) {
|
|
|
|
|
case Qt::DisplayRole:
|
2019-07-05 09:40:31 +02:00
|
|
|
return interpreter.name;
|
2022-03-28 10:07:31 +02:00
|
|
|
case Qt::FontRole: {
|
2019-07-05 09:40:31 +02:00
|
|
|
QFont f = font();
|
|
|
|
|
f.setBold(interpreter.id == m_defaultId);
|
|
|
|
|
return f;
|
|
|
|
|
}
|
2022-03-28 10:07:31 +02:00
|
|
|
case Qt::ToolTipRole:
|
|
|
|
|
if (interpreter.command.isEmpty())
|
2022-07-15 11:49:34 +02:00
|
|
|
return Tr::tr("Executable is empty.");
|
2022-03-28 10:07:31 +02:00
|
|
|
if (!interpreter.command.exists())
|
2022-07-15 11:49:34 +02:00
|
|
|
return Tr::tr("%1 does not exist.").arg(interpreter.command.toUserOutput());
|
2022-03-28 10:07:31 +02:00
|
|
|
if (!interpreter.command.isExecutableFile())
|
2022-07-15 11:49:34 +02:00
|
|
|
return Tr::tr("%1 is not an executable file.").arg(interpreter.command.toUserOutput());
|
2022-03-28 10:07:31 +02:00
|
|
|
break;
|
|
|
|
|
case Qt::DecorationRole:
|
|
|
|
|
if (column == 0 && !interpreter.command.isExecutableFile())
|
|
|
|
|
return Utils::Icons::CRITICAL.icon();
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
2019-07-05 09:40:31 +02:00
|
|
|
return {};
|
|
|
|
|
});
|
2022-09-02 10:19:45 +02:00
|
|
|
m_model.setAllData(PythonSettings::interpreters());
|
2019-07-05 09:40:31 +02:00
|
|
|
|
2022-11-17 14:58:11 +01:00
|
|
|
auto addButton = new QPushButton(Tr::tr("&Add"), this);
|
2021-03-11 18:43:47 +01:00
|
|
|
|
2022-11-17 14:58:11 +01:00
|
|
|
m_deleteButton = new QPushButton(Tr::tr("&Delete"), this);
|
2019-07-05 09:40:31 +02:00
|
|
|
m_deleteButton->setEnabled(false);
|
2022-07-15 11:49:34 +02:00
|
|
|
m_makeDefaultButton = new QPushButton(Tr::tr("&Make Default"));
|
2019-07-05 09:40:31 +02:00
|
|
|
m_makeDefaultButton->setEnabled(false);
|
2021-03-11 18:43:47 +01:00
|
|
|
|
2022-11-17 14:58:11 +01:00
|
|
|
m_cleanButton = new QPushButton(Tr::tr("&Clean Up"), this);
|
2022-07-15 11:49:34 +02:00
|
|
|
m_cleanButton->setToolTip(Tr::tr("Remove all Python interpreters without a valid executable."));
|
2022-03-28 13:24:24 +02:00
|
|
|
|
2022-11-17 14:58:11 +01:00
|
|
|
m_view = new QTreeView(this);
|
2021-03-11 18:43:47 +01:00
|
|
|
|
|
|
|
|
Column buttons {
|
|
|
|
|
addButton,
|
|
|
|
|
m_deleteButton,
|
|
|
|
|
m_makeDefaultButton,
|
2022-03-28 13:24:24 +02:00
|
|
|
m_cleanButton,
|
2022-07-22 18:54:04 +02:00
|
|
|
st
|
2021-03-11 18:43:47 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Column {
|
2022-11-17 14:58:11 +01:00
|
|
|
Row { m_view, buttons },
|
2021-03-11 18:43:47 +01:00
|
|
|
m_detailsWidget
|
|
|
|
|
}.attachTo(this);
|
2022-07-15 11:49:34 +02:00
|
|
|
|
2022-11-17 14:58:11 +01:00
|
|
|
updateCleanButton();
|
|
|
|
|
|
|
|
|
|
m_detailsWidget->hide();
|
|
|
|
|
|
|
|
|
|
m_view->setModel(&m_model);
|
|
|
|
|
m_view->setHeaderHidden(true);
|
|
|
|
|
m_view->setSelectionMode(QAbstractItemView::SingleSelection);
|
|
|
|
|
m_view->setSelectionBehavior(QAbstractItemView::SelectItems);
|
|
|
|
|
|
2022-07-15 11:49:34 +02:00
|
|
|
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);
|
2022-11-17 14:58:11 +01:00
|
|
|
connect(m_view->selectionModel(), &QItemSelectionModel::currentChanged,
|
2022-07-15 11:49:34 +02:00
|
|
|
this, &InterpreterOptionsWidget::currentChanged);
|
2019-07-05 09:40:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void InterpreterOptionsWidget::apply()
|
2022-08-26 15:16:08 +02:00
|
|
|
{
|
|
|
|
|
PythonSettings::setInterpreter(interpreters(), m_defaultId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void InterpreterOptionsWidget::addInterpreter(const Interpreter &interpreter)
|
|
|
|
|
{
|
|
|
|
|
m_model.appendItem(interpreter);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void InterpreterOptionsWidget::removeInterpreterFrom(const QString &detectionSource)
|
|
|
|
|
{
|
|
|
|
|
m_model.destroyItems(Utils::equal(&Interpreter::detectionSource, detectionSource));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QList<Interpreter> InterpreterOptionsWidget::interpreters() const
|
2019-07-05 09:40:31 +02:00
|
|
|
{
|
|
|
|
|
QList<Interpreter> interpreters;
|
2020-07-22 13:50:25 +02:00
|
|
|
for (const TreeItem *treeItem : m_model)
|
|
|
|
|
interpreters << static_cast<const ListItem<Interpreter> *>(treeItem)->itemData;
|
2022-08-26 15:16:08 +02:00
|
|
|
return interpreters;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QList<Interpreter> InterpreterOptionsWidget::interpreterFrom(const QString &detectionSource) const
|
|
|
|
|
{
|
|
|
|
|
return m_model.allData(Utils::equal(&Interpreter::detectionSource, detectionSource));
|
2019-07-05 09:40:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-28 10:07:31 +02:00
|
|
|
void InterpreterOptionsWidget::detailsChanged()
|
|
|
|
|
{
|
2022-11-17 14:58:11 +01:00
|
|
|
const QModelIndex &index = m_view->currentIndex();
|
2022-03-28 10:07:31 +02:00
|
|
|
if (index.isValid()) {
|
|
|
|
|
m_model.itemAt(index.row())->itemData = m_detailsWidget->toInterpreter();
|
|
|
|
|
emit m_model.dataChanged(index, index);
|
|
|
|
|
}
|
2022-03-28 13:24:24 +02:00
|
|
|
updateCleanButton();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void InterpreterOptionsWidget::updateCleanButton()
|
|
|
|
|
{
|
|
|
|
|
m_cleanButton->setEnabled(Utils::anyOf(m_model.allData(), [](const Interpreter &interpreter) {
|
|
|
|
|
return !interpreter.command.isExecutableFile();
|
|
|
|
|
}));
|
2022-03-28 10:07:31 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-05 09:40:31 +02:00
|
|
|
void InterpreterOptionsWidget::addItem()
|
|
|
|
|
{
|
|
|
|
|
const QModelIndex &index = m_model.indexForItem(
|
2022-03-28 09:27:54 +02:00
|
|
|
m_model.appendItem({QUuid::createUuid().toString(), QString("Python"), FilePath(), false}));
|
2019-07-05 09:40:31 +02:00
|
|
|
QTC_ASSERT(index.isValid(), return);
|
2022-11-17 14:58:11 +01:00
|
|
|
m_view->setCurrentIndex(index);
|
2022-03-28 13:24:24 +02:00
|
|
|
updateCleanButton();
|
2019-07-05 09:40:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void InterpreterOptionsWidget::deleteItem()
|
|
|
|
|
{
|
2022-11-17 14:58:11 +01:00
|
|
|
const QModelIndex &index = m_view->currentIndex();
|
2019-07-05 09:40:31 +02:00
|
|
|
if (index.isValid())
|
|
|
|
|
m_model.destroyItem(m_model.itemAt(index.row()));
|
2022-03-28 13:24:24 +02:00
|
|
|
updateCleanButton();
|
2019-07-05 09:40:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class InterpreterOptionsPage : public Core::IOptionsPage
|
|
|
|
|
{
|
|
|
|
|
public:
|
2022-09-02 10:19:45 +02:00
|
|
|
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");
|
|
|
|
|
setWidgetCreator([]() { return new InterpreterOptionsWidget(); });
|
|
|
|
|
}
|
2022-08-26 15:16:08 +02:00
|
|
|
|
|
|
|
|
QList<Interpreter> interpreters()
|
|
|
|
|
{
|
|
|
|
|
if (auto w = static_cast<InterpreterOptionsWidget *>(widget()))
|
|
|
|
|
return w->interpreters();
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void addInterpreter(const Interpreter &interpreter)
|
|
|
|
|
{
|
|
|
|
|
if (auto w = static_cast<InterpreterOptionsWidget *>(widget()))
|
|
|
|
|
w->addInterpreter(interpreter);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void removeInterpreterFrom(const QString &detectionSource)
|
|
|
|
|
{
|
|
|
|
|
if (auto w = static_cast<InterpreterOptionsWidget *>(widget()))
|
|
|
|
|
w->removeInterpreterFrom(detectionSource);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QList<Interpreter> interpreterFrom(const QString &detectionSource)
|
|
|
|
|
{
|
|
|
|
|
if (auto w = static_cast<InterpreterOptionsWidget *>(widget()))
|
|
|
|
|
return w->interpreterFrom(detectionSource);
|
|
|
|
|
return {};
|
|
|
|
|
}
|
2019-07-05 09:40:31 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-07 17:36:24 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-02 10:19:45 +02:00
|
|
|
class PyLSConfigureWidget : public Core::IOptionsPageWidget
|
2022-06-07 17:36:24 +02:00
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
PyLSConfigureWidget()
|
|
|
|
|
: m_editor(LanguageClient::jsonEditor())
|
|
|
|
|
, m_advancedLabel(new QLabel)
|
2022-07-15 11:49:34 +02:00
|
|
|
, m_pluginsGroup(new QGroupBox(Tr::tr("Plugins:")))
|
|
|
|
|
, m_mainGroup(new QGroupBox(Tr::tr("Use Python Language Server")))
|
2022-06-07 17:36:24 +02:00
|
|
|
|
|
|
|
|
{
|
2022-06-09 13:41:41 +02:00
|
|
|
m_mainGroup->setCheckable(true);
|
2022-06-07 17:36:24 +02:00
|
|
|
|
2022-06-09 13:41:41 +02:00
|
|
|
auto mainGroupLayout = new QVBoxLayout;
|
|
|
|
|
|
|
|
|
|
auto pluginsLayout = new QVBoxLayout;
|
2022-06-07 17:36:24 +02:00
|
|
|
m_pluginsGroup->setLayout(pluginsLayout);
|
2022-06-09 13:41:41 +02:00
|
|
|
m_pluginsGroup->setFlat(true);
|
2022-06-07 17:36:24 +02:00
|
|
|
for (const QString &plugin : plugins()) {
|
|
|
|
|
auto checkBox = new QCheckBox(plugin, this);
|
2022-06-09 13:41:41 +02:00
|
|
|
connect(checkBox, &QCheckBox::clicked, this, [this, plugin, checkBox]() {
|
2022-06-07 17:36:24 +02:00
|
|
|
updatePluginEnabled(checkBox->checkState(), plugin);
|
|
|
|
|
});
|
|
|
|
|
m_checkBoxes[plugin] = checkBox;
|
2022-06-09 13:41:41 +02:00
|
|
|
pluginsLayout->addWidget(checkBox);
|
2022-06-07 17:36:24 +02:00
|
|
|
}
|
2022-06-09 13:41:41 +02:00
|
|
|
|
|
|
|
|
mainGroupLayout->addWidget(m_pluginsGroup);
|
2022-06-07 17:36:24 +02:00
|
|
|
|
2022-07-15 11:49:34 +02:00
|
|
|
const QString labelText = Tr::tr(
|
2022-06-29 09:32:36 +02:00
|
|
|
"For a complete list of available options, consult the <a "
|
2022-06-07 17:36:24 +02:00
|
|
|
"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);
|
2022-06-09 13:41:41 +02:00
|
|
|
mainGroupLayout->addWidget(m_advancedLabel);
|
|
|
|
|
mainGroupLayout->addWidget(m_editor->editorWidget(), 1);
|
2022-06-07 17:36:24 +02:00
|
|
|
|
2022-06-09 13:41:41 +02:00
|
|
|
mainGroupLayout->addStretch();
|
2022-06-07 17:36:24 +02:00
|
|
|
|
2022-07-15 11:49:34 +02:00
|
|
|
auto advanced = new QCheckBox(Tr::tr("Advanced"));
|
2022-06-07 17:36:24 +02:00
|
|
|
advanced->setChecked(false);
|
2022-06-09 13:41:41 +02:00
|
|
|
mainGroupLayout->addWidget(advanced);
|
|
|
|
|
|
|
|
|
|
m_mainGroup->setLayout(mainGroupLayout);
|
|
|
|
|
|
|
|
|
|
QVBoxLayout *mainLayout = new QVBoxLayout;
|
|
|
|
|
mainLayout->addWidget(m_mainGroup);
|
2022-06-07 17:36:24 +02:00
|
|
|
setLayout(mainLayout);
|
|
|
|
|
|
2022-09-02 10:19:45 +02:00
|
|
|
m_editor->textDocument()->setPlainText(PythonSettings::pylsConfiguration());
|
|
|
|
|
m_mainGroup->setChecked(PythonSettings::pylsEnabled());
|
2022-06-07 17:36:24 +02:00
|
|
|
updateCheckboxes();
|
2022-11-17 14:58:11 +01:00
|
|
|
|
|
|
|
|
setAdvanced(false);
|
|
|
|
|
|
|
|
|
|
connect(advanced,
|
|
|
|
|
&QCheckBox::toggled,
|
|
|
|
|
this,
|
|
|
|
|
&PyLSConfigureWidget::setAdvanced);
|
|
|
|
|
|
2022-06-07 17:36:24 +02:00
|
|
|
}
|
|
|
|
|
|
2022-09-02 10:19:45 +02:00
|
|
|
void apply() override
|
2022-06-07 17:36:24 +02:00
|
|
|
{
|
2022-06-09 13:41:41 +02:00
|
|
|
PythonSettings::setPylsEnabled(m_mainGroup->isChecked());
|
2022-06-07 17:36:24 +02:00
|
|
|
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;
|
2022-06-09 13:41:41 +02:00
|
|
|
QGroupBox *m_mainGroup = nullptr;
|
2022-06-07 17:36:24 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PyLSOptionsPage : public Core::IOptionsPage
|
|
|
|
|
{
|
|
|
|
|
public:
|
2022-09-02 10:19:45 +02:00
|
|
|
PyLSOptionsPage()
|
|
|
|
|
{
|
|
|
|
|
setId(Constants::C_PYLSCONFIGURATION_PAGE_ID);
|
|
|
|
|
setDisplayName(Tr::tr("Language Server Configuration"));
|
|
|
|
|
setCategory(Constants::C_PYTHON_SETTINGS_CATEGORY);
|
|
|
|
|
setWidgetCreator([]() {return new PyLSConfigureWidget();});
|
2022-06-07 17:36:24 +02:00
|
|
|
}
|
2022-09-02 10:19:45 +02:00
|
|
|
};
|
2022-06-09 13:41:41 +02:00
|
|
|
|
2022-06-07 17:36:24 +02:00
|
|
|
static PyLSOptionsPage &pylspOptionsPage()
|
|
|
|
|
{
|
|
|
|
|
static PyLSOptionsPage page;
|
|
|
|
|
return page;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-05 09:40:31 +02:00
|
|
|
void InterpreterOptionsWidget::makeDefault()
|
|
|
|
|
{
|
2022-11-17 14:58:11 +01:00
|
|
|
const QModelIndex &index = m_view->currentIndex();
|
2019-07-05 09:40:31 +02:00
|
|
|
if (index.isValid()) {
|
2020-05-20 08:16:19 +02:00
|
|
|
QModelIndex defaultIndex = m_model.findIndex([this](const Interpreter &interpreter) {
|
|
|
|
|
return interpreter.id == m_defaultId;
|
|
|
|
|
});
|
2019-07-05 09:40:31 +02:00
|
|
|
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});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-28 13:24:24 +02:00
|
|
|
void InterpreterOptionsWidget::cleanUp()
|
|
|
|
|
{
|
|
|
|
|
m_model.destroyItems(
|
|
|
|
|
[](const Interpreter &interpreter) { return !interpreter.command.isExecutableFile(); });
|
|
|
|
|
updateCleanButton();
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-05 09:40:31 +02:00
|
|
|
constexpr char settingsGroupKey[] = "Python";
|
|
|
|
|
constexpr char interpreterKey[] = "Interpeter";
|
|
|
|
|
constexpr char defaultKey[] = "DefaultInterpeter";
|
2022-06-09 13:41:41 +02:00
|
|
|
constexpr char pylsEnabledKey[] = "PylsEnabled";
|
2022-06-07 17:36:24 +02:00
|
|
|
constexpr char pylsConfigurationKey[] = "PylsConfiguration";
|
2019-07-05 09:40:31 +02:00
|
|
|
|
2022-06-07 17:36:24 +02:00
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-22 13:46:50 +02:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-05 09:40:31 +02:00
|
|
|
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(),
|
2022-07-15 11:49:34 +02:00
|
|
|
name + Tr::tr(" (Windowed)"),
|
2019-07-05 09:40:31 +02:00
|
|
|
FilePath::fromUserInput(regVal.toString())};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
regVal = pythonRegistry.value("InstallPath/.");
|
|
|
|
|
if (regVal.isValid()) {
|
|
|
|
|
const FilePath &path = FilePath::fromUserInput(regVal.toString());
|
2021-08-11 08:28:29 +02:00
|
|
|
const FilePath python = path.pathAppended("python").withExecutableSuffix();
|
2019-07-05 09:40:31 +02:00
|
|
|
if (python.exists() && !alreadyRegistered(pythons, python))
|
2022-04-13 12:26:54 +02:00
|
|
|
pythons << createInterpreter(python, "Python " + versionGroup);
|
2021-08-11 08:28:29 +02:00
|
|
|
const FilePath pythonw = path.pathAppended("pythonw").withExecutableSuffix();
|
2019-07-05 09:40:31 +02:00
|
|
|
if (pythonw.exists() && !alreadyRegistered(pythons, pythonw))
|
2022-08-26 15:16:08 +02:00
|
|
|
pythons << createInterpreter(pythonw, "Python " + versionGroup, "(Windowed)");
|
2019-07-05 09:40:31 +02:00
|
|
|
}
|
|
|
|
|
pythonRegistry.endGroup();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void addPythonsFromPath(QList<Interpreter> &pythons)
|
|
|
|
|
{
|
|
|
|
|
const auto &env = Environment::systemEnvironment();
|
|
|
|
|
|
|
|
|
|
if (HostOsInfo::isWindowsHost()) {
|
|
|
|
|
for (const FilePath &executable : env.findAllInPath("python")) {
|
2020-11-10 10:28:10 +01:00
|
|
|
// Windows creates empty redirector files that may interfere
|
|
|
|
|
if (executable.toFileInfo().size() == 0)
|
|
|
|
|
continue;
|
2019-07-05 09:40:31 +02:00
|
|
|
if (executable.exists() && !alreadyRegistered(pythons, executable))
|
2022-04-13 12:26:54 +02:00
|
|
|
pythons << createInterpreter(executable, "Python from Path");
|
2019-07-05 09:40:31 +02:00
|
|
|
}
|
|
|
|
|
for (const FilePath &executable : env.findAllInPath("pythonw")) {
|
|
|
|
|
if (executable.exists() && !alreadyRegistered(pythons, executable))
|
2022-08-26 15:16:08 +02:00
|
|
|
pythons << createInterpreter(executable, "Python from Path", "(Windowed)");
|
2019-07-05 09:40:31 +02:00
|
|
|
}
|
|
|
|
|
} 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))
|
2022-04-13 12:26:54 +02:00
|
|
|
pythons << createInterpreter(executable, "Python from Path");
|
2019-07-05 09:40:31 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-02 10:19:45 +02:00
|
|
|
static QString idForPythonFromPath(const QList<Interpreter> &pythons)
|
2019-07-05 09:40:31 +02:00
|
|
|
{
|
2021-12-09 12:46:24 +01:00
|
|
|
FilePath pythonFromPath = Environment::systemEnvironment().searchInPath("python3");
|
|
|
|
|
if (pythonFromPath.isEmpty())
|
|
|
|
|
pythonFromPath = Environment::systemEnvironment().searchInPath("python");
|
2019-07-05 09:40:31 +02:00
|
|
|
if (pythonFromPath.isEmpty())
|
|
|
|
|
return {};
|
|
|
|
|
const Interpreter &defaultInterpreter
|
|
|
|
|
= findOrDefault(pythons, [pythonFromPath](const Interpreter &interpreter) {
|
|
|
|
|
return interpreter.command == pythonFromPath;
|
|
|
|
|
});
|
|
|
|
|
return defaultInterpreter.id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static PythonSettings *settingsInstance = nullptr;
|
|
|
|
|
|
2022-09-02 10:19:45 +02:00
|
|
|
PythonSettings::PythonSettings()
|
2019-07-05 09:40:31 +02:00
|
|
|
{
|
2022-11-16 15:07:36 +01:00
|
|
|
QTC_ASSERT(!settingsInstance, return);
|
|
|
|
|
settingsInstance = this;
|
|
|
|
|
|
2022-08-26 15:16:08 +02:00
|
|
|
setObjectName("PythonSettings");
|
|
|
|
|
ExtensionSystem::PluginManager::addObject(this);
|
|
|
|
|
|
2022-09-02 10:19:45 +02:00
|
|
|
initFromSettings(Core::ICore::settings());
|
|
|
|
|
|
|
|
|
|
if (HostOsInfo::isWindowsHost())
|
|
|
|
|
addPythonsFromRegistry(m_interpreters);
|
|
|
|
|
addPythonsFromPath(m_interpreters);
|
2019-07-05 09:40:31 +02:00
|
|
|
|
2022-09-02 10:19:45 +02:00
|
|
|
if (m_defaultInterpreterId.isEmpty())
|
|
|
|
|
m_defaultInterpreterId = idForPythonFromPath(m_interpreters);
|
2022-06-07 17:36:24 +02:00
|
|
|
|
2022-09-02 10:19:45 +02:00
|
|
|
writeToSettings(Core::ICore::settings());
|
2019-07-05 09:40:31 +02:00
|
|
|
|
2022-09-02 10:19:45 +02:00
|
|
|
interpreterOptionsPage();
|
|
|
|
|
pylspOptionsPage();
|
|
|
|
|
}
|
2019-07-05 09:40:31 +02:00
|
|
|
|
2022-09-06 13:44:53 +02:00
|
|
|
PythonSettings::~PythonSettings()
|
|
|
|
|
{
|
|
|
|
|
ExtensionSystem::PluginManager::removeObject(this);
|
|
|
|
|
settingsInstance = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-05 09:40:31 +02:00
|
|
|
void PythonSettings::setInterpreter(const QList<Interpreter> &interpreters, const QString &defaultId)
|
|
|
|
|
{
|
2022-09-02 10:19:45 +02:00
|
|
|
if (defaultId == settingsInstance->m_defaultInterpreterId
|
|
|
|
|
&& interpreters == settingsInstance->m_interpreters) {
|
2019-10-30 07:47:19 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2022-09-02 10:19:45 +02:00
|
|
|
settingsInstance->m_interpreters = interpreters;
|
|
|
|
|
settingsInstance->m_defaultInterpreterId = defaultId;
|
2019-10-25 09:15:59 +02:00
|
|
|
saveSettings();
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-07 17:36:24 +02:00
|
|
|
void PythonSettings::setPyLSConfiguration(const QString &configuration)
|
|
|
|
|
{
|
2022-09-02 10:19:45 +02:00
|
|
|
if (configuration == settingsInstance->m_pylsConfiguration)
|
2022-06-07 17:36:24 +02:00
|
|
|
return;
|
2022-09-02 10:19:45 +02:00
|
|
|
settingsInstance->m_pylsConfiguration = configuration;
|
2022-06-07 17:36:24 +02:00
|
|
|
saveSettings();
|
|
|
|
|
emit instance()->pylsConfigurationChanged(configuration);
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-09 13:41:41 +02:00
|
|
|
void PythonSettings::setPylsEnabled(const bool &enabled)
|
|
|
|
|
{
|
2022-09-02 10:19:45 +02:00
|
|
|
if (enabled == settingsInstance->m_pylsEnabled)
|
2022-06-09 13:41:41 +02:00
|
|
|
return;
|
2022-09-02 10:19:45 +02:00
|
|
|
settingsInstance->m_pylsEnabled = enabled;
|
2022-06-09 13:41:41 +02:00
|
|
|
saveSettings();
|
|
|
|
|
emit instance()->pylsEnabledChanged(enabled);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool PythonSettings::pylsEnabled()
|
|
|
|
|
{
|
2022-09-02 10:19:45 +02:00
|
|
|
return settingsInstance->m_pylsEnabled;
|
2022-06-09 13:41:41 +02:00
|
|
|
}
|
|
|
|
|
|
2022-09-02 09:26:12 +02:00
|
|
|
QString PythonSettings::pylsConfiguration()
|
2022-06-07 17:36:24 +02:00
|
|
|
{
|
2022-09-02 10:19:45 +02:00
|
|
|
return settingsInstance->m_pylsConfiguration;
|
2022-06-07 17:36:24 +02:00
|
|
|
}
|
|
|
|
|
|
2019-10-25 09:15:59 +02:00
|
|
|
void PythonSettings::addInterpreter(const Interpreter &interpreter, bool isDefault)
|
|
|
|
|
{
|
2022-09-02 10:19:45 +02:00
|
|
|
if (Utils::anyOf(settingsInstance->m_interpreters, Utils::equal(&Interpreter::id, interpreter.id)))
|
|
|
|
|
return;
|
|
|
|
|
settingsInstance->m_interpreters.append(interpreter);
|
2019-10-25 09:15:59 +02:00
|
|
|
if (isDefault)
|
2022-09-02 10:19:45 +02:00
|
|
|
settingsInstance->m_defaultInterpreterId = interpreter.id;
|
2019-10-25 09:15:59 +02:00
|
|
|
saveSettings();
|
2019-07-05 09:40:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PythonSettings *PythonSettings::instance()
|
|
|
|
|
{
|
|
|
|
|
QTC_CHECK(settingsInstance);
|
|
|
|
|
return settingsInstance;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-25 09:15:59 +02:00
|
|
|
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()) {
|
2022-04-13 12:26:54 +02:00
|
|
|
interpreter = createInterpreter(python, defaultName);
|
2019-10-25 09:15:59 +02:00
|
|
|
PythonSettings::addInterpreter(interpreter);
|
|
|
|
|
}
|
|
|
|
|
result << interpreter;
|
|
|
|
|
} else {
|
|
|
|
|
dir.cdUp();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
dir.cdUp();
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-08-03 12:02:37 +02:00
|
|
|
} while (dir.cdUp() && !(dir.isRoot() && Utils::HostOsInfo::isAnyUnixHost()));
|
2019-10-25 09:15:59 +02:00
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-02 10:19:45 +02:00
|
|
|
void PythonSettings::initFromSettings(QSettings *settings)
|
|
|
|
|
{
|
|
|
|
|
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)
|
|
|
|
|
m_interpreters << interpreter;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-07 14:46:06 +02:00
|
|
|
for (const Interpreter &interpreter : std::as_const(oldSettings)) {
|
2022-09-02 10:19:45 +02:00
|
|
|
if (Utils::anyOf(m_interpreters, Utils::equal(&Interpreter::id, interpreter.id)))
|
|
|
|
|
continue;
|
|
|
|
|
m_interpreters << interpreter;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-17 12:34:38 +02:00
|
|
|
const auto keepInterpreter = [](const Interpreter &interpreter) {
|
|
|
|
|
return !interpreter.autoDetected // always keep user added interpreters
|
|
|
|
|
|| interpreter.command.needsDevice() // remote devices might not be reachable at startup
|
|
|
|
|
|| interpreter.command.isExecutableFile();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
m_interpreters = Utils::filtered(m_interpreters, keepInterpreter);
|
2022-09-02 10:19:45 +02:00
|
|
|
|
|
|
|
|
m_defaultInterpreterId = settings->value(defaultKey).toString();
|
|
|
|
|
|
|
|
|
|
QVariant pylsEnabled = settings->value(pylsEnabledKey);
|
|
|
|
|
if (pylsEnabled.isNull())
|
|
|
|
|
disableOutdatedPyls();
|
|
|
|
|
else
|
|
|
|
|
m_pylsEnabled = pylsEnabled.toBool();
|
|
|
|
|
const QVariant pylsConfiguration = settings->value(pylsConfigurationKey);
|
|
|
|
|
if (!pylsConfiguration.isNull())
|
|
|
|
|
m_pylsConfiguration = pylsConfiguration.toString();
|
|
|
|
|
else
|
|
|
|
|
m_pylsConfiguration = defaultPylsConfiguration();
|
|
|
|
|
settings->endGroup();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PythonSettings::writeToSettings(QSettings *settings)
|
|
|
|
|
{
|
|
|
|
|
settings->beginGroup(settingsGroupKey);
|
|
|
|
|
QVariantList interpretersVar;
|
|
|
|
|
for (const Interpreter &interpreter : m_interpreters) {
|
|
|
|
|
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, m_defaultInterpreterId);
|
|
|
|
|
settings->setValue(pylsConfigurationKey, m_pylsConfiguration);
|
|
|
|
|
settings->setValue(pylsEnabledKey, m_pylsEnabled);
|
|
|
|
|
settings->endGroup();
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-26 15:16:08 +02:00
|
|
|
void PythonSettings::detectPythonOnDevice(const Utils::FilePaths &searchPaths,
|
|
|
|
|
const QString &deviceName,
|
|
|
|
|
const QString &detectionSource,
|
|
|
|
|
QString *logMessage)
|
|
|
|
|
{
|
|
|
|
|
QStringList messages{tr("Searching Python binaries...")};
|
|
|
|
|
auto alreadyConfigured = interpreterOptionsPage().interpreters();
|
|
|
|
|
for (const FilePath &path : searchPaths) {
|
|
|
|
|
const FilePath python = path.pathAppended("python3").withExecutableSuffix();
|
|
|
|
|
if (!python.isExecutableFile())
|
|
|
|
|
continue;
|
|
|
|
|
if (Utils::contains(alreadyConfigured, Utils::equal(&Interpreter::command, python)))
|
|
|
|
|
continue;
|
|
|
|
|
auto interpreter = createInterpreter(python, "Python on", "on " + deviceName);
|
|
|
|
|
interpreter.detectionSource = detectionSource;
|
|
|
|
|
interpreterOptionsPage().addInterpreter(interpreter);
|
|
|
|
|
messages.append(tr("Found \"%1\" (%2)").arg(interpreter.name, python.toUserOutput()));
|
|
|
|
|
}
|
|
|
|
|
if (logMessage)
|
|
|
|
|
*logMessage = messages.join('\n');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PythonSettings::removeDetectedPython(const QString &detectionSource, QString *logMessage)
|
|
|
|
|
{
|
|
|
|
|
if (logMessage)
|
|
|
|
|
logMessage->append(Tr::tr("Removing Python") + '\n');
|
|
|
|
|
|
|
|
|
|
interpreterOptionsPage().removeInterpreterFrom(detectionSource);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PythonSettings::listDetectedPython(const QString &detectionSource, QString *logMessage)
|
|
|
|
|
{
|
|
|
|
|
if (!logMessage)
|
|
|
|
|
return;
|
|
|
|
|
logMessage->append(Tr::tr("Python:") + '\n');
|
|
|
|
|
for (Interpreter &interpreter: interpreterOptionsPage().interpreterFrom(detectionSource))
|
|
|
|
|
logMessage->append(interpreter.name + '\n');
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-25 09:15:59 +02:00
|
|
|
void PythonSettings::saveSettings()
|
|
|
|
|
{
|
2022-09-02 10:19:45 +02:00
|
|
|
QTC_ASSERT(settingsInstance, return);
|
|
|
|
|
settingsInstance->writeToSettings(Core::ICore::settings());
|
|
|
|
|
emit settingsInstance->interpretersChanged(settingsInstance->m_interpreters,
|
|
|
|
|
settingsInstance->m_defaultInterpreterId);
|
2019-10-25 09:15:59 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-05 09:40:31 +02:00
|
|
|
QList<Interpreter> PythonSettings::interpreters()
|
|
|
|
|
{
|
2022-09-02 10:19:45 +02:00
|
|
|
return settingsInstance->m_interpreters;
|
2019-07-05 09:40:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Interpreter PythonSettings::defaultInterpreter()
|
|
|
|
|
{
|
2022-09-02 10:19:45 +02:00
|
|
|
return interpreter(settingsInstance->m_defaultInterpreterId);
|
2019-07-05 09:40:31 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-16 09:35:02 +01:00
|
|
|
Interpreter PythonSettings::interpreter(const QString &interpreterId)
|
|
|
|
|
{
|
2022-09-02 10:19:45 +02:00
|
|
|
return Utils::findOrDefault(settingsInstance->m_interpreters,
|
|
|
|
|
Utils::equal(&Interpreter::id, interpreterId));
|
2022-03-16 09:35:02 +01:00
|
|
|
}
|
|
|
|
|
|
2022-07-15 11:49:34 +02:00
|
|
|
} // Python::Internal
|
2019-07-05 09:40:31 +02:00
|
|
|
|
|
|
|
|
#include "pythonsettings.moc"
|