CppEditor: Add settings for clangd session mode

Complete with (hidden) UI. Doesn't do anything yet, because some
assumptions about projects need to be adapted on the LanguageClient side
first.

Task-number: QTCREATORBUG-26526
Change-Id: I34c92555e34c3d3ed98462261d47b35dfc015ce0
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Christian Kandeler
2021-11-23 17:04:13 +01:00
parent cc7619804e
commit 0cc4617c78
7 changed files with 147 additions and 14 deletions

View File

@@ -31,6 +31,7 @@
#include <coreplugin/icore.h>
#include <projectexplorer/project.h>
#include <projectexplorer/session.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
@@ -77,6 +78,7 @@ static QString clangdHeaderInsertionKey() { return QLatin1String("ClangdHeaderIn
static QString clangdThreadLimitKey() { return QLatin1String("ClangdThreadLimit"); }
static QString clangdDocumentThresholdKey() { return QLatin1String("ClangdDocumentThreshold"); }
static QString clangdUseGlobalSettingsKey() { return QLatin1String("useGlobalSettings"); }
static QString sessionsWithOneClangdKey() { return QLatin1String("SessionsWithOneClangd"); }
static FilePath g_defaultClangdFilePath;
static FilePath fallbackClangdFilePath()
@@ -316,6 +318,20 @@ ClangdSettings &ClangdSettings::instance()
return settings;
}
ClangdSettings::ClangdSettings()
{
loadSettings();
const auto sessionMgr = ProjectExplorer::SessionManager::instance();
connect(sessionMgr, &ProjectExplorer::SessionManager::sessionRemoved,
this, [this](const QString &name) { m_data.sessionsWithOneClangd.removeOne(name); });
connect(sessionMgr, &ProjectExplorer::SessionManager::sessionRenamed,
this, [this](const QString &oldName, const QString &newName) {
const auto index = m_data.sessionsWithOneClangd.indexOf(oldName);
if (index != -1)
m_data.sessionsWithOneClangd[index] = newName;
});
}
bool ClangdSettings::useClangd() const
{
return m_data.useClangd && clangdVersion(clangdFilePath()) >= QVersionNumber(13);
@@ -333,6 +349,13 @@ FilePath ClangdSettings::clangdFilePath() const
return fallbackClangdFilePath();
}
ClangdSettings::Granularity ClangdSettings::granularity() const
{
if (m_data.sessionsWithOneClangd.contains(ProjectExplorer::SessionManager::activeSession()))
return Granularity::Session;
return Granularity::Project;
}
void ClangdSettings::setData(const Data &data)
{
if (this == &instance() && data != m_data) {
@@ -402,7 +425,12 @@ ClangdSettings::Data ClangdProjectSettings::settings() const
{
if (m_useGlobalSettings)
return ClangdSettings::instance().data();
return m_customSettings;
ClangdSettings::Data data = m_customSettings;
// This property is global by definition.
data.sessionsWithOneClangd = ClangdSettings::instance().data().sessionsWithOneClangd;
return data;
}
void ClangdProjectSettings::setSettings(const ClangdSettings::Data &data)
@@ -450,6 +478,7 @@ QVariantMap ClangdSettings::Data::toMap() const
map.insert(clangdHeaderInsertionKey(), autoIncludeHeaders);
map.insert(clangdThreadLimitKey(), workerThreadLimit);
map.insert(clangdDocumentThresholdKey(), documentUpdateThreshold);
map.insert(sessionsWithOneClangdKey(), sessionsWithOneClangd);
return map;
}
@@ -461,6 +490,7 @@ void ClangdSettings::Data::fromMap(const QVariantMap &map)
autoIncludeHeaders = map.value(clangdHeaderInsertionKey(), false).toBool();
workerThreadLimit = map.value(clangdThreadLimitKey(), 0).toInt();
documentUpdateThreshold = map.value(clangdDocumentThresholdKey(), 500).toInt();
sessionsWithOneClangd = map.value(sessionsWithOneClangdKey()).toStringList();
}
} // namespace CppEditor

View File

@@ -108,6 +108,7 @@ public:
void fromMap(const QVariantMap &map);
Utils::FilePath executableFilePath;
QStringList sessionsWithOneClangd;
int workerThreadLimit = 0;
bool useClangd = false;
bool enableIndexing = true;
@@ -127,6 +128,9 @@ public:
int workerThreadLimit() const { return m_data.workerThreadLimit; }
int documentUpdateThreshold() const { return m_data.documentUpdateThreshold; }
enum class Granularity { Project, Session };
Granularity granularity() const;
void setData(const Data &data);
Data data() const { return m_data; }
@@ -141,7 +145,7 @@ signals:
void changed();
private:
ClangdSettings() { loadSettings(); }
ClangdSettings();
void loadSettings();
void saveSettings();
@@ -153,6 +157,7 @@ inline bool operator==(const ClangdSettings::Data &s1, const ClangdSettings::Dat
{
return s1.useClangd == s2.useClangd
&& s1.executableFilePath == s2.executableFilePath
&& s1.sessionsWithOneClangd == s2.sessionsWithOneClangd
&& s1.workerThreadLimit == s2.workerThreadLimit
&& s1.enableIndexing == s2.enableIndexing
&& s1.autoIncludeHeaders == s2.autoIncludeHeaders

View File

@@ -32,13 +32,22 @@
#include "cpptoolsreuse.h"
#include <coreplugin/icore.h>
#include <projectexplorer/session.h>
#include <utils/algorithm.h>
#include <utils/infolabel.h>
#include <utils/itemviews.h>
#include <utils/pathchooser.h>
#include <utils/qtcassert.h>
#include <QFormLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QInputDialog>
#include <QPushButton>
#include <QSpinBox>
#include <QStringListModel>
#include <QTextStream>
#include <QVBoxLayout>
#include <QVersionNumber>
namespace CppEditor::Internal {
@@ -201,9 +210,12 @@ public:
QSpinBox documentUpdateThreshold;
Utils::PathChooser clangdChooser;
Utils::InfoLabel versionWarningLabel;
QGroupBox *sessionsGroupBox = nullptr;
QStringListModel sessionsModel;
};
ClangdSettingsWidget::ClangdSettingsWidget(const ClangdSettings::Data &settingsData)
ClangdSettingsWidget::ClangdSettingsWidget(const ClangdSettings::Data &settingsData,
bool isForProject)
: d(new Private)
{
const ClangdSettings settings(settingsData);
@@ -253,6 +265,64 @@ ClangdSettingsWidget::ClangdSettingsWidget(const ClangdSettings::Data &settingsD
const auto documentUpdateThresholdLabel = new QLabel(tr("Document update threshold:"));
formLayout->addRow(documentUpdateThresholdLabel, documentUpdateThresholdLayout);
layout->addLayout(formLayout);
if (!isForProject) {
d->sessionsModel.setStringList(settingsData.sessionsWithOneClangd);
d->sessionsModel.sort(0);
d->sessionsGroupBox = new QGroupBox(tr("Sessions with a single clangd instance"));
const auto sessionsView = new Utils::ListView;
sessionsView->setModel(&d->sessionsModel);
sessionsView->setToolTip(
tr("By default, Qt Creator runs one clangd process per project.\n"
"If you have sessions with tightly coupled projects that should be\n"
"managed by the same clangd process, add them here."));
const auto outerSessionsLayout = new QHBoxLayout;
const auto innerSessionsLayout = new QHBoxLayout(d->sessionsGroupBox);
const auto buttonsLayout = new QVBoxLayout;
const auto addButton = new QPushButton(tr("Add ..."));
const auto removeButton = new QPushButton(tr("Remove"));
buttonsLayout->addWidget(addButton);
buttonsLayout->addWidget(removeButton);
buttonsLayout->addStretch(1);
innerSessionsLayout->addWidget(sessionsView);
innerSessionsLayout->addLayout(buttonsLayout);
outerSessionsLayout->addWidget(d->sessionsGroupBox);
outerSessionsLayout->addStretch(1);
layout->addLayout(outerSessionsLayout);
const auto updateRemoveButtonState = [removeButton, sessionsView] {
removeButton->setEnabled(sessionsView->selectionModel()->hasSelection());
};
connect(sessionsView->selectionModel(), &QItemSelectionModel::selectionChanged,
this, updateRemoveButtonState);
updateRemoveButtonState();
connect(removeButton, &QPushButton::clicked, this, [this, sessionsView] {
const QItemSelection selection = sessionsView->selectionModel()->selection();
QTC_ASSERT(!selection.isEmpty(), return);
d->sessionsModel.removeRow(selection.indexes().first().row());
});
connect(addButton, &QPushButton::clicked, this, [this, sessionsView] {
QInputDialog dlg(sessionsView);
QStringList sessions = ProjectExplorer::SessionManager::sessions();
QStringList currentSessions = d->sessionsModel.stringList();
for (const QString &s : qAsConst(currentSessions))
sessions.removeOne(s);
if (sessions.isEmpty())
return;
sessions.sort();
dlg.setLabelText(tr("Choose a session:"));
dlg.setComboBoxItems(sessions);
if (dlg.exec() == QDialog::Accepted) {
currentSessions << dlg.textValue();
d->sessionsModel.setStringList(currentSessions);
d->sessionsModel.sort(0);
}
});
// TODO: Remove once the concept is functional.
d->sessionsGroupBox->hide();
}
layout->addStretch(1);
static const auto setWidgetsEnabled = [](QLayout *layout, bool enabled, const auto &f) -> void {
@@ -263,8 +333,10 @@ ClangdSettingsWidget::ClangdSettingsWidget(const ClangdSettings::Data &settingsD
f(l, enabled, f);
}
};
const auto toggleEnabled = [formLayout](const bool checked) {
const auto toggleEnabled = [this, formLayout](const bool checked) {
setWidgetsEnabled(formLayout, checked, setWidgetsEnabled);
if (d->sessionsGroupBox)
d->sessionsGroupBox->setEnabled(checked);
};
connect(&d->useClangdCheckBox, &QCheckBox::toggled, toggleEnabled);
toggleEnabled(d->useClangdCheckBox.isChecked());
@@ -326,6 +398,7 @@ ClangdSettings::Data ClangdSettingsWidget::settingsData() const
data.autoIncludeHeaders = d->autoIncludeHeadersCheckBox.isChecked();
data.workerThreadLimit = d->threadLimitSpinBox.value();
data.documentUpdateThreshold = d->documentUpdateThreshold.value();
data.sessionsWithOneClangd = d->sessionsModel.stringList();
return data;
}
@@ -334,7 +407,7 @@ class ClangdSettingsPageWidget final : public Core::IOptionsPageWidget
Q_DECLARE_TR_FUNCTIONS(CppEditor::Internal::ClangdSettingsWidget)
public:
ClangdSettingsPageWidget() : m_widget(ClangdSettings::instance().data())
ClangdSettingsPageWidget() : m_widget(ClangdSettings::instance().data(), false)
{
const auto layout = new QVBoxLayout(this);
layout->addWidget(&m_widget);
@@ -348,7 +421,7 @@ private:
ClangdSettingsPage::ClangdSettingsPage()
{
setId("K.Clangd");
setId(Constants::CPP_CLANGD_SETTINGS_ID);
setDisplayName(ClangdSettingsWidget::tr("Clangd"));
setCategory(Constants::CPP_SETTINGS_CATEGORY);
setWidgetCreator([] { return new ClangdSettingsPageWidget; });
@@ -358,7 +431,7 @@ ClangdSettingsPage::ClangdSettingsPage()
class ClangdProjectSettingsWidget::Private
{
public:
Private(const ClangdProjectSettings &s) : settings(s), widget(s.settings()) {}
Private(const ClangdProjectSettings &s) : settings(s), widget(s.settings(), true) {}
ClangdProjectSettings settings;
ClangdSettingsWidget widget;
@@ -369,16 +442,35 @@ ClangdProjectSettingsWidget::ClangdProjectSettingsWidget(const ClangdProjectSett
: d(new Private(settings))
{
const auto layout = new QVBoxLayout(this);
d->useGlobalSettingsCheckBox.setText(tr("Use global settings"));
layout->addWidget(&d->useGlobalSettingsCheckBox);
const auto globalSettingsLayout = new QHBoxLayout;
globalSettingsLayout->addWidget(&d->useGlobalSettingsCheckBox);
const auto globalSettingsLabel = new QLabel("Use <a href=\"dummy\">global settings</a>");
connect(globalSettingsLabel, &QLabel::linkActivated,
this, [] { Core::ICore::showOptionsDialog(Constants::CPP_CLANGD_SETTINGS_ID); });
globalSettingsLayout->addWidget(globalSettingsLabel);
globalSettingsLayout->addStretch(1);
layout->addLayout(globalSettingsLayout);
const auto separator = new QFrame;
separator->setFrameShape(QFrame::HLine);
layout->addWidget(separator);
layout->addWidget(&d->widget);
d->useGlobalSettingsCheckBox.setChecked(d->settings.useGlobalSettings());
d->widget.setEnabled(!d->settings.useGlobalSettings());
connect(&d->useGlobalSettingsCheckBox, &QCheckBox::toggled, [this](bool checked) {
const auto updateGlobalSettingsCheckBox = [this] {
if (ClangdSettings::instance().granularity() == ClangdSettings::Granularity::Session) {
d->useGlobalSettingsCheckBox.setEnabled(false);
d->useGlobalSettingsCheckBox.setChecked(true);
} else {
d->useGlobalSettingsCheckBox.setEnabled(true);
d->useGlobalSettingsCheckBox.setChecked(d->settings.useGlobalSettings());
}
d->widget.setEnabled(!d->useGlobalSettingsCheckBox.isChecked());
};
updateGlobalSettingsCheckBox();
connect(&ClangdSettings::instance(), &ClangdSettings::changed,
this, updateGlobalSettingsCheckBox);
connect(&d->useGlobalSettingsCheckBox, &QCheckBox::clicked, [this](bool checked) {
d->widget.setEnabled(!checked);
d->settings.setUseGlobalSettings(checked);
if (!checked)

View File

@@ -48,7 +48,7 @@ class ClangdSettingsWidget : public QWidget
Q_OBJECT
public:
ClangdSettingsWidget(const ClangdSettings::Data &settingsData);
ClangdSettingsWidget(const ClangdSettings::Data &settingsData, bool isForProject);
~ClangdSettingsWidget();
ClangdSettings::Data settingsData() const;

View File

@@ -119,6 +119,7 @@ const char CPP_FILE_SETTINGS_NAME[] = QT_TRANSLATE_NOOP("CppEditor", "File Namin
const char CPP_CODE_MODEL_SETTINGS_ID[] = "C.Cpp.Code Model";
const char CPP_DIAGNOSTIC_CONFIG_SETTINGS_ID[] = "C.Cpp.Diagnostic Config";
const char CPP_DIAGNOSTIC_CONFIG_SETTINGS_NAME[] = QT_TRANSLATE_NOOP("CppEditor", "Diagnostic Configurations");
const char CPP_CLANGD_SETTINGS_ID[] = "K.Cpp.Clangd";
const char CPP_SETTINGS_CATEGORY[] = "I.C++";
const char CPP_CLANG_FIXIT_AVAILABLE_MARKER_ID[] = "ClangFixItAvailableMarker";

View File

@@ -831,6 +831,7 @@ bool SessionManager::renameSession(const QString &original, const QString &newNa
return false;
if (original == activeSession())
loadSession(newName);
emit instance()->sessionRenamed(original, newName);
return deleteSession(original);
}
@@ -858,6 +859,7 @@ bool SessionManager::deleteSession(const QString &session)
if (!d->m_sessions.contains(session))
return false;
d->m_sessions.removeOne(session);
emit instance()->sessionRemoved(session);
QFile fi(sessionNameToFileName(session).toString());
if (fi.exists())
return fi.remove();

View File

@@ -141,7 +141,10 @@ signals:
void aboutToSaveSession();
void dependencyChanged(ProjectExplorer::Project *a, ProjectExplorer::Project *b);
signals: // for tests only
void sessionRenamed(const QString &oldName, const QString &newName);
void sessionRemoved(const QString &name);
// for tests only
void projectFinishedParsing(ProjectExplorer::Project *project);
private: