// Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include "clangformatconfigwidget.h" #include "clangformatconstants.h" #include "clangformatindenter.h" #include "clangformatfile.h" #include "clangformatsettings.h" #include "clangformatutils.h" // the file was generated by scripts/generateClangFormatChecksLayout.py #include "clangformatchecks.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ProjectExplorer; using namespace Utils; namespace ClangFormat { bool ClangFormatConfigWidget::eventFilter(QObject *object, QEvent *event) { if (event->type() == QEvent::Wheel && qobject_cast(object)) { event->ignore(); return true; } return QWidget::eventFilter(object, event); } ClangFormatConfigWidget::ClangFormatConfigWidget(TextEditor::ICodeStylePreferences *codeStyle, ProjectExplorer::Project *project, QWidget *parent) : CppCodeStyleWidget(parent) , m_project(project) { m_config = std::make_unique(filePathToCurrentSettings(codeStyle->currentPreferences())); resize(489, 305); m_fallbackConfig = new QLabel(tr("Clang-Format Style")); m_checksScrollArea = new QScrollArea(); m_checksWidget = new ClangFormatChecks(); m_checksScrollArea->setWidget(m_checksWidget); m_checksScrollArea->setWidgetResizable(true); m_checksWidget->setEnabled(!codeStyle->isReadOnly()); FilePath fileName; if (m_project) fileName = m_project->projectFilePath().pathAppended("snippet.cpp"); else fileName = Core::ICore::userResourcePath("snippet.cpp"); m_preview = new TextEditor::SnippetEditorWidget(this); TextEditor::DisplaySettings displaySettings = m_preview->displaySettings(); displaySettings.m_visualizeWhitespace = true; m_preview->setDisplaySettings(displaySettings); m_preview->setPlainText(QLatin1String(CppEditor::Constants::DEFAULT_CODE_STYLE_SNIPPETS[0])); m_preview->textDocument()->setIndenter(new ClangFormatIndenter(m_preview->document())); m_preview->textDocument()->setFontSettings(TextEditor::TextEditorSettings::fontSettings()); m_preview->textDocument()->setSyntaxHighlighter(new CppEditor::CppHighlighter); m_preview->textDocument()->indenter()->setFileName(fileName); using namespace Layouting; Column { m_fallbackConfig, Row { m_checksScrollArea, m_preview }, }.attachTo(this); connect(codeStyle, &TextEditor::ICodeStylePreferences::currentPreferencesChanged, this, &ClangFormatConfigWidget::slotCodeStyleChanged); slotCodeStyleChanged(codeStyle->currentPreferences()); showOrHideWidgets(); fillTable(); updatePreview(); connectChecks(); } ClangFormatConfigWidget::~ClangFormatConfigWidget() = default; void ClangFormatConfigWidget::slotCodeStyleChanged( TextEditor::ICodeStylePreferences *codeStyle) { if (!codeStyle) return; m_config.reset(new ClangFormatFile(filePathToCurrentSettings(codeStyle))); m_config->setIsReadOnly(codeStyle->isReadOnly()); m_style = m_config->style(); m_checksWidget->setEnabled(!codeStyle->isReadOnly()); fillTable(); updatePreview(); } void ClangFormatConfigWidget::connectChecks() { auto doSaveChanges = [this](QObject *sender) { if (!m_ignoreChanges.isLocked()) saveChanges(sender); }; for (QObject *child : m_checksWidget->children()) { auto comboBox = qobject_cast(child); if (comboBox != nullptr) { connect(comboBox, &QComboBox::currentIndexChanged, this, std::bind(doSaveChanges, comboBox)); comboBox->installEventFilter(this); continue; } const auto button = qobject_cast(child); if (button != nullptr) connect(button, &QPushButton::clicked, this, std::bind(doSaveChanges, button)); } } void ClangFormatConfigWidget::showOrHideWidgets() { auto verticalLayout = qobject_cast(layout()); QTC_ASSERT(verticalLayout, return); QLayoutItem *lastItem = verticalLayout->itemAt(verticalLayout->count() - 1); if (lastItem->spacerItem()) verticalLayout->removeItem(lastItem); createStyleFileIfNeeded(!m_project); m_fallbackConfig->show(); m_checksScrollArea->show(); m_preview->show(); } void ClangFormatConfigWidget::updatePreview() { QTextCursor cursor(m_preview->document()); cursor.setPosition(0); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); m_preview->textDocument()->autoIndent(cursor); } static inline void ltrim(std::string &s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { return !std::isspace(ch); })); } static inline void rtrim(std::string &s) { s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { return !std::isspace(ch); }).base(), s.end()); } static inline void trim(std::string &s) { ltrim(s); rtrim(s); } static void fillPlainText(QPlainTextEdit *plainText, const std::string &text, size_t index) { if (index == std::string::npos) { plainText->setPlainText(""); return; } size_t valueStart = text.find('\n', index + 1); size_t valueEnd; std::string value; QTC_ASSERT(valueStart != std::string::npos, return;); do { valueEnd = text.find('\n', valueStart + 1); if (valueEnd == std::string::npos) break; // Skip also 2 spaces - start with valueStart + 1 + 2. std::string line = text.substr(valueStart + 3, valueEnd - valueStart - 3); rtrim(line); value += value.empty() ? line : '\n' + line; valueStart = valueEnd; } while (valueEnd < text.size() - 1 && text.at(valueEnd + 1) == ' '); plainText->setPlainText(QString::fromStdString(value)); } static void fillComboBoxOrLineEdit(QObject *object, const std::string &text, size_t index) { auto *comboBox = qobject_cast(object); auto *lineEdit = qobject_cast(object); if (index == std::string::npos) { if (comboBox) comboBox->setCurrentIndex(0); else lineEdit->setText(""); return; } const size_t valueStart = text.find(':', index + 1); QTC_ASSERT(valueStart != std::string::npos, return;); const size_t valueEnd = text.find('\n', valueStart + 1); QTC_ASSERT(valueEnd != std::string::npos, return;); std::string value = text.substr(valueStart + 1, valueEnd - valueStart - 1); trim(value); if (comboBox) { if (comboBox->findText(QString::fromStdString(value)) == -1) { comboBox->setCurrentIndex(0); return; } comboBox->setCurrentText(QString::fromStdString(value)); return; } lineEdit->setText(QString::fromStdString(value)); } void ClangFormatConfigWidget::fillTable() { Utils::GuardLocker locker(m_ignoreChanges); const std::string configText = readFile(m_config->filePath().path()); for (QObject *child : m_checksWidget->children()) { if (!qobject_cast(child) && !qobject_cast(child) && !qobject_cast(child)) { continue; } size_t index = configText.find('\n' + child->objectName().toStdString()); if (index == std::string::npos) index = configText.find("\n " + child->objectName().toStdString()); if (qobject_cast(child)) fillPlainText(qobject_cast(child), configText, index); else fillComboBoxOrLineEdit(child, configText, index); } } void ClangFormatConfigWidget::saveChanges(QObject *sender) { if (sender->objectName() == "BasedOnStyle") { const auto *basedOnStyle = m_checksWidget->findChild("BasedOnStyle"); m_config->setBasedOnStyle(basedOnStyle->currentText()); } else { QList fields; for (QObject *child : m_checksWidget->children()) { if (child->objectName() == "BasedOnStyle") continue; auto *label = qobject_cast(child); if (!label) continue; QWidget *valueWidget = m_checksWidget->findChild(label->text().trimmed()); if (!valueWidget) { // Currently BraceWrapping only. fields.append({label->text(), ""}); continue; } if (!qobject_cast(valueWidget) && !qobject_cast(valueWidget) && !qobject_cast(valueWidget)) { continue; } auto *plainText = qobject_cast(valueWidget); if (plainText) { if (plainText->toPlainText().trimmed().isEmpty()) continue; std::stringstream content; QStringList list = plainText->toPlainText().split('\n'); for (const QString &line : list) content << "\n " << line.toStdString(); fields.append({label->text(), QString::fromStdString(content.str())}); } else { QString text; if (auto *comboBox = qobject_cast(valueWidget)) { text = comboBox->currentText(); } else { auto *lineEdit = qobject_cast(valueWidget); QTC_ASSERT(lineEdit, continue;); text = lineEdit->text(); } if (!text.isEmpty() && text != "Default") fields.append({label->text(), text}); } } m_config->changeFields(fields); } fillTable(); updatePreview(); synchronize(); } void ClangFormatConfigWidget::setCodeStyleSettings(const CppEditor::CppCodeStyleSettings &settings) { m_config->fromCppCodeStyleSettings(settings); fillTable(); updatePreview(); } void ClangFormatConfigWidget::setTabSettings(const TextEditor::TabSettings &settings) { m_config->fromTabSettings(settings); fillTable(); updatePreview(); } void ClangFormatConfigWidget::synchronize() { emit codeStyleSettingsChanged(m_config->toCppCodeStyleSettings(m_project)); emit tabSettingsChanged(m_config->toTabSettings(m_project)); } void ClangFormatConfigWidget::apply() { if (!m_checksWidget->isVisible() && !m_checksWidget->isEnabled()) return; m_style = m_config->style(); } void ClangFormatConfigWidget::finish() { if (!m_checksWidget->isVisible() && !m_checksWidget->isEnabled()) return; m_config->setStyle(m_style); } } // namespace ClangFormat