Python: globalize Python language server settings

Change-Id: I84fcee6462064f0c788492fcfa12a77379af2bd7
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
David Schulz
2022-06-07 17:36:24 +02:00
parent e42ff3f14e
commit ed22ef7854
7 changed files with 285 additions and 222 deletions

View File

@@ -37,6 +37,7 @@ const char C_EDITOR_DISPLAY_NAME[] =
QT_TRANSLATE_NOOP("OpenWith::Editors", "Python Editor");
const char C_PYTHONOPTIONS_PAGE_ID[] = "PythonEditor.OptionsPage";
const char C_PYLSCONFIGURATION_PAGE_ID[] = "PythonEditor.PythonLanguageServerConfiguration";
const char C_PYTHON_SETTINGS_CATEGORY[] = "P.Python";
const char PYTHON_OPEN_REPL[] = "Python.OpenRepl";

View File

@@ -53,7 +53,6 @@
#include <QCheckBox>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QFutureWatcher>
#include <QGridLayout>
#include <QGroupBox>
@@ -164,154 +163,6 @@ static PythonLanguageServerState checkPythonLanguageServer(const FilePath &pytho
return {PythonLanguageServerState::CanNotBeInstalled, FilePath()};
}
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 PylsConfigureDialog : public QDialog
{
Q_DECLARE_TR_FUNCTIONS(PylsConfigureDialog)
public:
PylsConfigureDialog()
: QDialog(Core::ICore::dialogParent())
, m_editor(jsonEditor())
, m_advancedLabel(new QLabel)
, m_pluginsGroup(new QGroupBox(tr("Plugins:")))
{
auto mainLayout = new QVBoxLayout;
auto pluginsLayout = new QGridLayout;
m_pluginsGroup->setLayout(pluginsLayout);
int i = 0;
for (const QString &plugin : plugins()) {
auto checkBox = new QCheckBox(plugin, this);
connect(checkBox, &QCheckBox::toggled, this, [this, plugin](bool enabled) {
updatePluginEnabled(enabled, plugin);
});
m_checkBoxes[plugin] = checkBox;
pluginsLayout->addWidget(checkBox, i / 4, i % 4);
++i;
}
mainLayout->addWidget(m_pluginsGroup);
const QString labelText = tr(
"For a complete list of avilable 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);
mainLayout->addWidget(m_advancedLabel);
mainLayout->addWidget(m_editor->editorWidget(), 1);
setAdvanced(false);
mainLayout->addStretch();
auto buttons = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
auto advanced = new QPushButton(tr("Advanced"));
advanced->setCheckable(true);
advanced->setChecked(false);
buttons->addButton(advanced, QDialogButtonBox::ActionRole);
connect(advanced,
&QPushButton::toggled,
this,
&PylsConfigureDialog::setAdvanced);
connect(buttons->button(QDialogButtonBox::Cancel),
&QPushButton::clicked,
this,
&QDialog::reject);
connect(buttons->button(QDialogButtonBox::Ok),
&QPushButton::clicked,
this,
&QDialog::accept);
mainLayout->addWidget(buttons);
setLayout(mainLayout);
resize(640, 480);
}
void setConfiguration(const QString &configuration)
{
m_editor->textDocument()->setPlainText(configuration);
updateCheckboxes();
}
QString configuration() const { return 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(bool enabled, const QString &plugin)
{
QJsonDocument document = QJsonDocument::fromJson(
m_editor->textDocument()->plainText().toUtf8());
if (document.isNull())
return;
QJsonObject config = document.object();
QJsonObject pylsp = config["pylsp"].toObject();
QJsonObject plugins = pylsp["plugins"].toObject();
QJsonObject pluginValue = plugins[plugin].toObject();
pluginValue.insert("enabled", enabled);
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;
};
class PyLSSettingsWidget : public QWidget
{
Q_DECLARE_TR_FUNCTIONS(PyLSSettingsWidget)
@@ -321,7 +172,6 @@ public:
, m_name(new QLineEdit(settings->m_name, this))
, m_interpreter(new QComboBox(this))
, m_configure(new QPushButton(tr("Configure..."), this))
, m_configuration(settings->m_configuration)
{
int row = 0;
auto *mainLayout = new QGridLayout;
@@ -345,7 +195,7 @@ public:
this,
&PyLSSettingsWidget::updateInterpreters);
connect(m_configure, &QPushButton::clicked, this, &PyLSSettingsWidget::showConfigureDialog);
connect(m_configure, &QPushButton::clicked, this, &PyLSSettingsWidget::switchToPylsConfigurePage);
}
void updateInterpreters(const QList<Interpreter> &interpreters, const QString &defaultId)
@@ -367,21 +217,16 @@ public:
QString name() const { return m_name->text(); }
QString interpreterId() const { return m_interpreter->currentData().toString(); }
QString configuration() const { return m_configuration; }
private:
void showConfigureDialog()
void switchToPylsConfigurePage()
{
PylsConfigureDialog dialog;
dialog.setConfiguration(m_configuration);
if (dialog.exec() == QDialog::Accepted)
m_configuration = dialog.configuration();
Core::ICore::showOptionsDialog(Constants::C_PYLSCONFIGURATION_PAGE_ID);
}
QLineEdit *m_name = nullptr;
QComboBox *m_interpreter = nullptr;
QPushButton *m_configure = nullptr;
QString m_configuration;
};
PyLSSettings::PyLSSettings()
@@ -392,8 +237,6 @@ PyLSSettings::PyLSSettings()
m_languageFilter.mimeTypes = QStringList()
<< Constants::C_PY_MIMETYPE << Constants::C_PY3_MIMETYPE;
m_arguments = "-m pylsp";
const QJsonDocument config(defaultConfiguration());
m_configuration = QString::fromUtf8(config.toJson());
}
bool PyLSSettings::isValid() const
@@ -413,10 +256,6 @@ QVariantMap PyLSSettings::toMap() const
void PyLSSettings::fromMap(const QVariantMap &map)
{
StdIOSettings::fromMap(map);
if (m_configuration.isEmpty()) {
const QJsonDocument config(defaultConfiguration());
m_configuration = QString::fromUtf8(config.toJson());
}
m_languageFilter.mimeTypes = QStringList()
<< Constants::C_PY_MIMETYPE << Constants::C_PY3_MIMETYPE;
setInterpreter(map[interpreterKey].toString());
@@ -433,15 +272,6 @@ bool PyLSSettings::applyFromSettingsWidget(QWidget *widget)
changed |= m_interpreterId != pylswidget->interpreterId();
setInterpreter(pylswidget->interpreterId());
if (m_configuration != pylswidget->configuration()) {
m_configuration = pylswidget->configuration();
if (!changed) { // if only the settings configuration changed just send an update
const QList<Client *> clients = LanguageClientManager::clientsForSetting(this);
for (Client *client : clients)
client->updateConfiguration(configuration());
}
}
return changed;
}
@@ -493,6 +323,18 @@ PyLSClient::PyLSClient(BaseClientInterface *interface)
: Client(interface)
, m_extraCompilerOutputDir(static_cast<PyLSInterface *>(interface)->m_extraPythonPath.path())
{
connect(this, &Client::initialized, this, &PyLSClient::updateConfiguration);
connect(PythonSettings::instance(), &PythonSettings::pylsConfigurationChanged,
this, &PyLSClient::updateConfiguration);
}
void PyLSClient::updateConfiguration()
{
const auto doc = QJsonDocument::fromJson(PythonSettings::pyLSConfiguration().toUtf8());
if (doc.isArray())
Client::updateConfiguration(doc.array());
else if (doc.isObject())
Client::updateConfiguration(doc.object());
}
void PyLSClient::openDocument(TextEditor::TextDocument *document)
@@ -583,36 +425,6 @@ Client *PyLSSettings::createClient(BaseClientInterface *interface) const
return new PyLSClient(interface);
}
QJsonObject PyLSSettings::defaultConfiguration()
{
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 configuration;
}
PyLSConfigureAssistant *PyLSConfigureAssistant::instance()
{
static auto *instance = new PyLSConfigureAssistant(PythonPlugin::instance());

View File

@@ -56,6 +56,7 @@ public:
static PyLSClient *clientForPython(const Utils::FilePath &python);
private:
void updateConfiguration();
void updateExtraCompilerContents(ProjectExplorer::ExtraCompiler *compiler,
const Utils::FilePath &file);
void closeExtraDoc(const Utils::FilePath &file);
@@ -87,8 +88,6 @@ private:
LanguageClient::BaseClientInterface *createInterface(
ProjectExplorer::Project *project) const override;
static QJsonObject defaultConfiguration();
QString m_interpreterId;
PyLSSettings(const PyLSSettings &other) = default;

View File

@@ -29,7 +29,9 @@
#include <coreplugin/dialogs/ioptionspage.h>
#include <coreplugin/icore.h>
#include <languageclient/languageclientsettings.h>
#include <texteditor/textdocument.h>
#include <texteditor/texteditor.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <utils/detailswidget.h>
@@ -49,6 +51,11 @@
#include <QStackedWidget>
#include <QTreeView>
#include <QWidget>
#include <QVBoxLayout>
#include <QGroupBox>
#include <QCheckBox>
#include <QJsonDocument>
#include <QJsonObject>
using namespace ProjectExplorer;
using namespace Utils;
@@ -364,6 +371,193 @@ static InterpreterOptionsPage &interpreterOptionsPage()
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("Plugins:")))
{
auto mainLayout = new QVBoxLayout;
auto pluginsLayout = new QGridLayout;
m_pluginsGroup->setLayout(pluginsLayout);
int i = 0;
for (const QString &plugin : plugins()) {
auto checkBox = new QCheckBox(plugin, this);
connect(checkBox, &QCheckBox::clicked, this, [this, plugin, checkBox](bool enabled) {
updatePluginEnabled(checkBox->checkState(), plugin);
});
m_checkBoxes[plugin] = checkBox;
pluginsLayout->addWidget(checkBox, i / 4, i % 4);
++i;
}
mainLayout->addWidget(m_pluginsGroup);
const QString labelText = tr(
"For a complete list of avilable 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);
mainLayout->addWidget(m_advancedLabel);
mainLayout->addWidget(m_editor->editorWidget(), 1);
setAdvanced(false);
mainLayout->addStretch();
auto advanced = new QCheckBox(tr("Advanced"));
advanced->setChecked(false);
connect(advanced,
&QCheckBox::toggled,
this,
&PyLSConfigureWidget::setAdvanced);
mainLayout->addWidget(advanced);
setLayout(mainLayout);
}
void setConfiguration(const QString &configuration)
{
m_editor->textDocument()->setPlainText(configuration);
updateCheckboxes();
}
void apply()
{
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;
};
class PyLSOptionsPage : public Core::IOptionsPage
{
public:
PyLSOptionsPage();
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;
QString m_configuration;
};
PyLSOptionsPage::PyLSOptionsPage()
{
setId(Constants::C_PYLSCONFIGURATION_PAGE_ID);
setDisplayName(PythonSettings::tr("Language Server Configuration"));
setCategory(Constants::C_PYTHON_SETTINGS_CATEGORY);
}
QWidget *PyLSOptionsPage::widget()
{
if (!m_widget) {
m_widget = new PyLSConfigureWidget();
m_widget->setConfiguration(m_configuration);
}
return m_widget;
}
void PyLSOptionsPage::apply()
{
if (m_widget)
m_widget->apply();
}
void PyLSOptionsPage::finish()
{
delete m_widget;
m_widget = nullptr;
}
static PyLSOptionsPage &pylspOptionsPage()
{
static PyLSOptionsPage page;
return page;
}
void InterpreterOptionsWidget::makeDefault()
{
const QModelIndex &index = m_view.currentIndex();
@@ -388,16 +582,48 @@ void InterpreterOptionsWidget::cleanUp()
constexpr char settingsGroupKey[] = "Python";
constexpr char interpreterKey[] = "Interpeter";
constexpr char defaultKey[] = "DefaultInterpeter";
constexpr char pylsConfigurationKey[] = "PylsConfiguration";
struct SavedSettings
{
QList<Interpreter> pythons;
QString defaultId;
QString pylsConfiguration;
};
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 SavedSettings fromSettings(QSettings *settings)
{
QList<Interpreter> pythons;
SavedSettings result;
settings->beginGroup(settingsGroupKey);
const QVariantList interpreters = settings->value(interpreterKey).toList();
QList<Interpreter> oldSettings;
@@ -410,24 +636,28 @@ static SavedSettings fromSettings(QSettings *settings)
if (interpreterList.size() == 3)
oldSettings << interpreter;
else if (interpreterList.size() == 4)
pythons << interpreter;
result.pythons << interpreter;
}
for (const Interpreter &interpreter : qAsConst(oldSettings)) {
if (Utils::anyOf(pythons, Utils::equal(&Interpreter::id, interpreter.id)))
if (Utils::anyOf(result.pythons, Utils::equal(&Interpreter::id, interpreter.id)))
continue;
pythons << interpreter;
result.pythons << interpreter;
}
pythons = Utils::filtered(pythons, [](const Interpreter &interpreter){
result.pythons = Utils::filtered(result.pythons, [](const Interpreter &interpreter){
return !interpreter.autoDetected || interpreter.command.isExecutableFile();
});
const QString defaultId = settings->value(defaultKey).toString();
result.defaultId = settings->value(defaultKey).toString();
const QVariant pylsConfiguration = settings->value(pylsConfigurationKey);
if (!pylsConfiguration.isNull())
result.pylsConfiguration = pylsConfiguration.toString();
else
result.pylsConfiguration = defaultPylsConfiguration();
settings->endGroup();
return {pythons, defaultId};
return result;
}
static void toSettings(QSettings *settings, const SavedSettings &savedSettings)
@@ -444,6 +674,7 @@ static void toSettings(QSettings *settings, const SavedSettings &savedSettings)
}
settings->setValue(interpreterKey, interpretersVar);
settings->setValue(defaultKey, savedSettings.defaultId);
settings->setValue(pylsConfigurationKey, savedSettings.pylsConfiguration);
settings->endGroup();
}
@@ -541,6 +772,8 @@ void PythonSettings::init()
settingsInstance = new PythonSettings();
const SavedSettings &settings = fromSettings(Core::ICore::settings());
pylspOptionsPage().setConfiguration(settings.pylsConfiguration);
QList<Interpreter> pythons = settings.pythons;
if (HostOsInfo::isWindowsHost())
@@ -563,6 +796,20 @@ void PythonSettings::setInterpreter(const QList<Interpreter> &interpreters, cons
saveSettings();
}
void PythonSettings::setPyLSConfiguration(const QString &configuration)
{
if (configuration == pylspOptionsPage().configuration())
return;
pylspOptionsPage().setConfiguration(configuration);
saveSettings();
emit instance()->pylsConfigurationChanged(configuration);
}
QString PythonSettings::pyLSConfiguration()
{
return pylspOptionsPage().configuration();
}
void PythonSettings::addInterpreter(const Interpreter &interpreter, bool isDefault)
{
interpreterOptionsPage().addInterpreter(interpreter);
@@ -618,7 +865,8 @@ void PythonSettings::saveSettings()
{
const QList<Interpreter> &interpreters = interpreterOptionsPage().interpreters();
const QString defaultId = interpreterOptionsPage().defaultInterpreter().id;
toSettings(Core::ICore::settings(), {interpreters, defaultId});
const QString pylsConfiguration = pylspOptionsPage().configuration();
toSettings(Core::ICore::settings(), {interpreters, defaultId, pylsConfiguration});
if (QTC_GUARD(settingsInstance))
emit settingsInstance->interpretersChanged(interpreters, defaultId);
}

View File

@@ -48,12 +48,15 @@ public:
static Interpreter interpreter(const QString &interpreterId);
static void setInterpreter(const QList<Interpreter> &interpreters, const QString &defaultId);
static void addInterpreter(const Interpreter &interpreter, bool isDefault = false);
static void setPyLSConfiguration(const QString &configuration);
static QString pyLSConfiguration();
static PythonSettings *instance();
static QList<Interpreter> detectPythonVenvs(const Utils::FilePath &path);
signals:
void interpretersChanged(const QList<Interpreter> &interpreters, const QString &defaultId);
void pylsConfigurationChanged(const QString &configuration);
private:
PythonSettings();