forked from qt-creator/qt-creator
Python: add lsp configuration widget
Change-Id: I688981293e3bbbee64fc2d714146fcdfa8126458 Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
@@ -1579,6 +1579,12 @@ void Client::initializeCallback(const InitializeRequest::Response &initResponse)
|
||||
}
|
||||
}
|
||||
|
||||
if (const BaseSettings *settings = LanguageClientManager::settingForClient(this)) {
|
||||
const QJsonValue configuration = settings->configuration();
|
||||
if (!configuration.isNull())
|
||||
updateConfiguration(configuration);
|
||||
}
|
||||
|
||||
for (TextEditor::TextDocument *doc : m_postponedDocuments)
|
||||
openDocument(doc);
|
||||
m_postponedDocuments.clear();
|
||||
|
@@ -37,6 +37,9 @@
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/session.h>
|
||||
|
||||
#include <texteditor/plaintexteditorfactory.h>
|
||||
#include <texteditor/textmark.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/utilsicons.h>
|
||||
#include <utils/delegates.h>
|
||||
@@ -75,6 +78,7 @@ constexpr char startupBehaviorKey[] = "startupBehavior";
|
||||
constexpr char mimeTypeKey[] = "mimeType";
|
||||
constexpr char filePatternKey[] = "filePattern";
|
||||
constexpr char initializationOptionsKey[] = "initializationOptions";
|
||||
constexpr char configurationKey[] = "configuration";
|
||||
constexpr char executableKey[] = "executable";
|
||||
constexpr char argumentsKey[] = "arguments";
|
||||
constexpr char settingsGroupKey[] = "LanguageClient";
|
||||
@@ -523,6 +527,16 @@ QJsonObject BaseSettings::initializationOptions() const
|
||||
expand(m_initializationOptions).toUtf8()).object();
|
||||
}
|
||||
|
||||
QJsonValue BaseSettings::configuration() const
|
||||
{
|
||||
const QJsonDocument document = QJsonDocument::fromJson(m_configuration.toUtf8());
|
||||
if (document.isArray())
|
||||
return document.array();
|
||||
if (document.isObject())
|
||||
return document.object();
|
||||
return {};
|
||||
}
|
||||
|
||||
bool BaseSettings::applyFromSettingsWidget(QWidget *widget)
|
||||
{
|
||||
bool changed = false;
|
||||
@@ -593,6 +607,7 @@ QVariantMap BaseSettings::toMap() const
|
||||
map.insert(mimeTypeKey, m_languageFilter.mimeTypes);
|
||||
map.insert(filePatternKey, m_languageFilter.filePattern);
|
||||
map.insert(initializationOptionsKey, m_initializationOptions);
|
||||
map.insert(configurationKey, m_configuration);
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -607,6 +622,7 @@ void BaseSettings::fromMap(const QVariantMap &map)
|
||||
m_languageFilter.filePattern = map[filePatternKey].toStringList();
|
||||
m_languageFilter.filePattern.removeAll(QString()); // remove empty entries
|
||||
m_initializationOptions = map[initializationOptionsKey].toString();
|
||||
m_configuration = map[configurationKey].toString();
|
||||
}
|
||||
|
||||
static LanguageClientSettingsPage &settingsPage()
|
||||
@@ -1025,4 +1041,39 @@ bool LanguageFilter::operator!=(const LanguageFilter &other) const
|
||||
return this->filePattern != other.filePattern || this->mimeTypes != other.mimeTypes;
|
||||
}
|
||||
|
||||
TextEditor::BaseTextEditor *jsonEditor()
|
||||
{
|
||||
using namespace TextEditor;
|
||||
BaseTextEditor *editor = PlainTextEditorFactory::createPlainTextEditor();
|
||||
TextDocument *document = editor->textDocument();
|
||||
TextEditorWidget *widget = editor->editorWidget();
|
||||
widget->configureGenericHighlighter(Utils::mimeTypeForName("application/json"));
|
||||
widget->setLineNumbersVisible(false);
|
||||
widget->setMarksVisible(false);
|
||||
widget->setRevisionsVisible(false);
|
||||
widget->setCodeFoldingSupported(false);
|
||||
QObject::connect(document, &TextDocument::contentsChanged, widget, [document](){
|
||||
const Utils::Id jsonMarkId("LanguageClient.JsonTextMarkId");
|
||||
qDeleteAll(
|
||||
Utils::filtered(document->marks(), Utils::equal(&TextMark::category, jsonMarkId)));
|
||||
const QString content = document->plainText().trimmed();
|
||||
if (content.isEmpty())
|
||||
return;
|
||||
QJsonParseError error;
|
||||
QJsonDocument::fromJson(content.toUtf8(), &error);
|
||||
if (error.error == QJsonParseError::NoError)
|
||||
return;
|
||||
const Utils::OptionalLineColumn lineColumn
|
||||
= Utils::Text::convertPosition(document->document(), error.offset);
|
||||
if (!lineColumn.has_value())
|
||||
return;
|
||||
auto mark = new TextMark(Utils::FilePath(), lineColumn->line, jsonMarkId);
|
||||
mark->setLineAnnotation(error.errorString());
|
||||
mark->setColor(Utils::Theme::CodeModel_Error_TextMarkColor);
|
||||
mark->setIcon(Utils::Icons::CODEMODEL_ERROR.icon());
|
||||
document->addMark(mark);
|
||||
});
|
||||
return editor;
|
||||
}
|
||||
|
||||
} // namespace LanguageClient
|
||||
|
@@ -51,6 +51,7 @@ class FancyLineEdit;
|
||||
|
||||
namespace Core { class IDocument; }
|
||||
namespace ProjectExplorer { class Project; }
|
||||
namespace TextEditor { class BaseTextEditor; }
|
||||
|
||||
namespace LanguageClient {
|
||||
|
||||
@@ -88,8 +89,10 @@ public:
|
||||
StartBehavior m_startBehavior = RequiresFile;
|
||||
LanguageFilter m_languageFilter;
|
||||
QString m_initializationOptions;
|
||||
QString m_configuration;
|
||||
|
||||
QJsonObject initializationOptions() const;
|
||||
QJsonValue configuration() const;
|
||||
|
||||
virtual bool applyFromSettingsWidget(QWidget *widget);
|
||||
virtual QWidget *createSettingsWidget(QWidget *parent = nullptr) const;
|
||||
@@ -212,4 +215,6 @@ private:
|
||||
QLineEdit *m_arguments = nullptr;
|
||||
};
|
||||
|
||||
LANGUAGECLIENT_EXPORT TextEditor::BaseTextEditor *jsonEditor();
|
||||
|
||||
} // namespace LanguageClient
|
||||
|
@@ -31,20 +31,26 @@
|
||||
#include "pythonutils.h"
|
||||
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <coreplugin/progressmanager/progressmanager.h>
|
||||
|
||||
#include <languageclient/languageclientmanager.h>
|
||||
|
||||
#include <texteditor/textdocument.h>
|
||||
#include <texteditor/texteditor.h>
|
||||
|
||||
#include <utils/infobar.h>
|
||||
#include <utils/qtcprocess.h>
|
||||
#include <utils/runextensions.h>
|
||||
#include <utils/variablechooser.h>
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFutureWatcher>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QPushButton>
|
||||
#include <QRegularExpression>
|
||||
#include <QTimer>
|
||||
|
||||
@@ -167,6 +173,154 @@ 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)
|
||||
@@ -175,6 +329,8 @@ public:
|
||||
: QWidget(parent)
|
||||
, 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;
|
||||
@@ -191,10 +347,14 @@ public:
|
||||
mainLayout->addWidget(m_interpreter, row, 1);
|
||||
setLayout(mainLayout);
|
||||
|
||||
mainLayout->addWidget(m_configure, ++row, 0);
|
||||
|
||||
connect(PythonSettings::instance(),
|
||||
&PythonSettings::interpretersChanged,
|
||||
this,
|
||||
&PyLSSettingsWidget::updateInterpreters);
|
||||
|
||||
connect(m_configure, &QPushButton::clicked, this, &PyLSSettingsWidget::showConfigureDialog);
|
||||
}
|
||||
|
||||
void updateInterpreters(const QList<Interpreter> &interpreters, const QString &defaultId)
|
||||
@@ -216,10 +376,21 @@ 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()
|
||||
{
|
||||
PylsConfigureDialog dialog;
|
||||
dialog.setConfiguration(m_configuration);
|
||||
if (dialog.exec() == QDialog::Accepted)
|
||||
m_configuration = dialog.configuration();
|
||||
}
|
||||
|
||||
QLineEdit *m_name = nullptr;
|
||||
QComboBox *m_interpreter = nullptr;
|
||||
QPushButton *m_configure = nullptr;
|
||||
QString m_configuration;
|
||||
};
|
||||
|
||||
PyLSSettings::PyLSSettings()
|
||||
@@ -229,6 +400,8 @@ PyLSSettings::PyLSSettings()
|
||||
m_startBehavior = RequiresFile;
|
||||
m_languageFilter.mimeTypes = QStringList(Constants::C_PY_MIMETYPE);
|
||||
m_arguments = "-m pylsp";
|
||||
const QJsonDocument config(defaultConfiguration());
|
||||
m_configuration = QString::fromUtf8(config.toJson());
|
||||
}
|
||||
|
||||
bool PyLSSettings::isValid() const
|
||||
@@ -248,6 +421,10 @@ 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());
|
||||
}
|
||||
setInterpreter(map[interpreterKey].toString());
|
||||
}
|
||||
|
||||
@@ -262,6 +439,15 @@ 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 QVector<Client *> clients = LanguageClientManager::clientForSetting(this);
|
||||
for (Client *client : clients)
|
||||
client->updateConfiguration(configuration());
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
@@ -290,6 +476,36 @@ Client *PyLSSettings::createClient(BaseClientInterface *interface) const
|
||||
return new Client(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());
|
||||
|
@@ -57,6 +57,8 @@ public:
|
||||
LanguageClient::Client *createClient(LanguageClient::BaseClientInterface *interface) const final;
|
||||
|
||||
private:
|
||||
static QJsonObject defaultConfiguration();
|
||||
|
||||
QString m_interpreterId;
|
||||
|
||||
PyLSSettings(const PyLSSettings &other) = default;
|
||||
|
Reference in New Issue
Block a user