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 <coreplugin/icore.h>
#include <projectexplorer/project.h> #include <projectexplorer/project.h>
#include <projectexplorer/session.h>
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
@@ -77,6 +78,7 @@ static QString clangdHeaderInsertionKey() { return QLatin1String("ClangdHeaderIn
static QString clangdThreadLimitKey() { return QLatin1String("ClangdThreadLimit"); } static QString clangdThreadLimitKey() { return QLatin1String("ClangdThreadLimit"); }
static QString clangdDocumentThresholdKey() { return QLatin1String("ClangdDocumentThreshold"); } static QString clangdDocumentThresholdKey() { return QLatin1String("ClangdDocumentThreshold"); }
static QString clangdUseGlobalSettingsKey() { return QLatin1String("useGlobalSettings"); } static QString clangdUseGlobalSettingsKey() { return QLatin1String("useGlobalSettings"); }
static QString sessionsWithOneClangdKey() { return QLatin1String("SessionsWithOneClangd"); }
static FilePath g_defaultClangdFilePath; static FilePath g_defaultClangdFilePath;
static FilePath fallbackClangdFilePath() static FilePath fallbackClangdFilePath()
@@ -316,6 +318,20 @@ ClangdSettings &ClangdSettings::instance()
return settings; 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 bool ClangdSettings::useClangd() const
{ {
return m_data.useClangd && clangdVersion(clangdFilePath()) >= QVersionNumber(13); return m_data.useClangd && clangdVersion(clangdFilePath()) >= QVersionNumber(13);
@@ -333,6 +349,13 @@ FilePath ClangdSettings::clangdFilePath() const
return fallbackClangdFilePath(); 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) void ClangdSettings::setData(const Data &data)
{ {
if (this == &instance() && data != m_data) { if (this == &instance() && data != m_data) {
@@ -402,7 +425,12 @@ ClangdSettings::Data ClangdProjectSettings::settings() const
{ {
if (m_useGlobalSettings) if (m_useGlobalSettings)
return ClangdSettings::instance().data(); 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) void ClangdProjectSettings::setSettings(const ClangdSettings::Data &data)
@@ -450,6 +478,7 @@ QVariantMap ClangdSettings::Data::toMap() const
map.insert(clangdHeaderInsertionKey(), autoIncludeHeaders); map.insert(clangdHeaderInsertionKey(), autoIncludeHeaders);
map.insert(clangdThreadLimitKey(), workerThreadLimit); map.insert(clangdThreadLimitKey(), workerThreadLimit);
map.insert(clangdDocumentThresholdKey(), documentUpdateThreshold); map.insert(clangdDocumentThresholdKey(), documentUpdateThreshold);
map.insert(sessionsWithOneClangdKey(), sessionsWithOneClangd);
return map; return map;
} }
@@ -461,6 +490,7 @@ void ClangdSettings::Data::fromMap(const QVariantMap &map)
autoIncludeHeaders = map.value(clangdHeaderInsertionKey(), false).toBool(); autoIncludeHeaders = map.value(clangdHeaderInsertionKey(), false).toBool();
workerThreadLimit = map.value(clangdThreadLimitKey(), 0).toInt(); workerThreadLimit = map.value(clangdThreadLimitKey(), 0).toInt();
documentUpdateThreshold = map.value(clangdDocumentThresholdKey(), 500).toInt(); documentUpdateThreshold = map.value(clangdDocumentThresholdKey(), 500).toInt();
sessionsWithOneClangd = map.value(sessionsWithOneClangdKey()).toStringList();
} }
} // namespace CppEditor } // namespace CppEditor

View File

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

View File

@@ -32,13 +32,22 @@
#include "cpptoolsreuse.h" #include "cpptoolsreuse.h"
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <projectexplorer/session.h>
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/infolabel.h> #include <utils/infolabel.h>
#include <utils/itemviews.h>
#include <utils/pathchooser.h> #include <utils/pathchooser.h>
#include <utils/qtcassert.h>
#include <QFormLayout> #include <QFormLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QInputDialog>
#include <QPushButton>
#include <QSpinBox> #include <QSpinBox>
#include <QStringListModel>
#include <QTextStream> #include <QTextStream>
#include <QVBoxLayout>
#include <QVersionNumber> #include <QVersionNumber>
namespace CppEditor::Internal { namespace CppEditor::Internal {
@@ -201,9 +210,12 @@ public:
QSpinBox documentUpdateThreshold; QSpinBox documentUpdateThreshold;
Utils::PathChooser clangdChooser; Utils::PathChooser clangdChooser;
Utils::InfoLabel versionWarningLabel; 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) : d(new Private)
{ {
const ClangdSettings settings(settingsData); const ClangdSettings settings(settingsData);
@@ -253,6 +265,64 @@ ClangdSettingsWidget::ClangdSettingsWidget(const ClangdSettings::Data &settingsD
const auto documentUpdateThresholdLabel = new QLabel(tr("Document update threshold:")); const auto documentUpdateThresholdLabel = new QLabel(tr("Document update threshold:"));
formLayout->addRow(documentUpdateThresholdLabel, documentUpdateThresholdLayout); formLayout->addRow(documentUpdateThresholdLabel, documentUpdateThresholdLayout);
layout->addLayout(formLayout); 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); layout->addStretch(1);
static const auto setWidgetsEnabled = [](QLayout *layout, bool enabled, const auto &f) -> void { 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); f(l, enabled, f);
} }
}; };
const auto toggleEnabled = [formLayout](const bool checked) { const auto toggleEnabled = [this, formLayout](const bool checked) {
setWidgetsEnabled(formLayout, checked, setWidgetsEnabled); setWidgetsEnabled(formLayout, checked, setWidgetsEnabled);
if (d->sessionsGroupBox)
d->sessionsGroupBox->setEnabled(checked);
}; };
connect(&d->useClangdCheckBox, &QCheckBox::toggled, toggleEnabled); connect(&d->useClangdCheckBox, &QCheckBox::toggled, toggleEnabled);
toggleEnabled(d->useClangdCheckBox.isChecked()); toggleEnabled(d->useClangdCheckBox.isChecked());
@@ -326,6 +398,7 @@ ClangdSettings::Data ClangdSettingsWidget::settingsData() const
data.autoIncludeHeaders = d->autoIncludeHeadersCheckBox.isChecked(); data.autoIncludeHeaders = d->autoIncludeHeadersCheckBox.isChecked();
data.workerThreadLimit = d->threadLimitSpinBox.value(); data.workerThreadLimit = d->threadLimitSpinBox.value();
data.documentUpdateThreshold = d->documentUpdateThreshold.value(); data.documentUpdateThreshold = d->documentUpdateThreshold.value();
data.sessionsWithOneClangd = d->sessionsModel.stringList();
return data; return data;
} }
@@ -334,7 +407,7 @@ class ClangdSettingsPageWidget final : public Core::IOptionsPageWidget
Q_DECLARE_TR_FUNCTIONS(CppEditor::Internal::ClangdSettingsWidget) Q_DECLARE_TR_FUNCTIONS(CppEditor::Internal::ClangdSettingsWidget)
public: public:
ClangdSettingsPageWidget() : m_widget(ClangdSettings::instance().data()) ClangdSettingsPageWidget() : m_widget(ClangdSettings::instance().data(), false)
{ {
const auto layout = new QVBoxLayout(this); const auto layout = new QVBoxLayout(this);
layout->addWidget(&m_widget); layout->addWidget(&m_widget);
@@ -348,7 +421,7 @@ private:
ClangdSettingsPage::ClangdSettingsPage() ClangdSettingsPage::ClangdSettingsPage()
{ {
setId("K.Clangd"); setId(Constants::CPP_CLANGD_SETTINGS_ID);
setDisplayName(ClangdSettingsWidget::tr("Clangd")); setDisplayName(ClangdSettingsWidget::tr("Clangd"));
setCategory(Constants::CPP_SETTINGS_CATEGORY); setCategory(Constants::CPP_SETTINGS_CATEGORY);
setWidgetCreator([] { return new ClangdSettingsPageWidget; }); setWidgetCreator([] { return new ClangdSettingsPageWidget; });
@@ -358,7 +431,7 @@ ClangdSettingsPage::ClangdSettingsPage()
class ClangdProjectSettingsWidget::Private class ClangdProjectSettingsWidget::Private
{ {
public: public:
Private(const ClangdProjectSettings &s) : settings(s), widget(s.settings()) {} Private(const ClangdProjectSettings &s) : settings(s), widget(s.settings(), true) {}
ClangdProjectSettings settings; ClangdProjectSettings settings;
ClangdSettingsWidget widget; ClangdSettingsWidget widget;
@@ -369,16 +442,35 @@ ClangdProjectSettingsWidget::ClangdProjectSettingsWidget(const ClangdProjectSett
: d(new Private(settings)) : d(new Private(settings))
{ {
const auto layout = new QVBoxLayout(this); const auto layout = new QVBoxLayout(this);
d->useGlobalSettingsCheckBox.setText(tr("Use global settings")); const auto globalSettingsLayout = new QHBoxLayout;
layout->addWidget(&d->useGlobalSettingsCheckBox); 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; const auto separator = new QFrame;
separator->setFrameShape(QFrame::HLine); separator->setFrameShape(QFrame::HLine);
layout->addWidget(separator); layout->addWidget(separator);
layout->addWidget(&d->widget); layout->addWidget(&d->widget);
d->useGlobalSettingsCheckBox.setChecked(d->settings.useGlobalSettings()); const auto updateGlobalSettingsCheckBox = [this] {
d->widget.setEnabled(!d->settings.useGlobalSettings()); if (ClangdSettings::instance().granularity() == ClangdSettings::Granularity::Session) {
connect(&d->useGlobalSettingsCheckBox, &QCheckBox::toggled, [this](bool checked) { 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->widget.setEnabled(!checked);
d->settings.setUseGlobalSettings(checked); d->settings.setUseGlobalSettings(checked);
if (!checked) if (!checked)

View File

@@ -48,7 +48,7 @@ class ClangdSettingsWidget : public QWidget
Q_OBJECT Q_OBJECT
public: public:
ClangdSettingsWidget(const ClangdSettings::Data &settingsData); ClangdSettingsWidget(const ClangdSettings::Data &settingsData, bool isForProject);
~ClangdSettingsWidget(); ~ClangdSettingsWidget();
ClangdSettings::Data settingsData() const; 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_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_ID[] = "C.Cpp.Diagnostic Config";
const char CPP_DIAGNOSTIC_CONFIG_SETTINGS_NAME[] = QT_TRANSLATE_NOOP("CppEditor", "Diagnostic Configurations"); 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_SETTINGS_CATEGORY[] = "I.C++";
const char CPP_CLANG_FIXIT_AVAILABLE_MARKER_ID[] = "ClangFixItAvailableMarker"; 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; return false;
if (original == activeSession()) if (original == activeSession())
loadSession(newName); loadSession(newName);
emit instance()->sessionRenamed(original, newName);
return deleteSession(original); return deleteSession(original);
} }
@@ -858,6 +859,7 @@ bool SessionManager::deleteSession(const QString &session)
if (!d->m_sessions.contains(session)) if (!d->m_sessions.contains(session))
return false; return false;
d->m_sessions.removeOne(session); d->m_sessions.removeOne(session);
emit instance()->sessionRemoved(session);
QFile fi(sessionNameToFileName(session).toString()); QFile fi(sessionNameToFileName(session).toString());
if (fi.exists()) if (fi.exists())
return fi.remove(); return fi.remove();

View File

@@ -141,7 +141,10 @@ signals:
void aboutToSaveSession(); void aboutToSaveSession();
void dependencyChanged(ProjectExplorer::Project *a, ProjectExplorer::Project *b); 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); void projectFinishedParsing(ProjectExplorer::Project *project);
private: private: