// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "cppcodestylesettingspage.h" #include "cppcodeformatter.h" #include "cppcodestylepreferences.h" #include "cppcodestylesnippets.h" #include "cppeditorconstants.h" #include "cppeditortr.h" #include "cpppointerdeclarationformatter.h" #include "cpptoolssettings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace TextEditor; using namespace Utils; namespace CppEditor::Internal { static void applyRefactorings(QTextDocument *textDocument, TextEditorWidget *editor, const CppCodeStyleSettings &settings) { // Preprocess source CPlusPlus::Environment env; Preprocessor preprocess(nullptr, &env); FilePath noFileFile = FilePath::fromPathPart(u""); const QByteArray preprocessedSource = preprocess.run(noFileFile, textDocument->toPlainText().toUtf8()); Document::Ptr cppDocument = Document::create(noFileFile); cppDocument->setUtf8Source(preprocessedSource); cppDocument->parse(Document::ParseTranlationUnit); cppDocument->check(); CppRefactoringFilePtr cppRefactoringFile = CppRefactoringChanges::file(editor, cppDocument); // Run the formatter Overview overview; overview.showReturnTypes = true; overview.starBindFlags = {}; if (settings.bindStarToIdentifier) overview.starBindFlags |= Overview::BindToIdentifier; if (settings.bindStarToTypeName) overview.starBindFlags |= Overview::BindToTypeName; if (settings.bindStarToLeftSpecifier) overview.starBindFlags |= Overview::BindToLeftSpecifier; if (settings.bindStarToRightSpecifier) overview.starBindFlags |= Overview::BindToRightSpecifier; PointerDeclarationFormatter formatter(cppRefactoringFile, overview); Utils::ChangeSet change = formatter.format(cppDocument->translationUnit()->ast()); // Apply change change.apply(textDocument); } // ------------------ CppCodeStyleSettingsWidget class CppCodeStylePreferencesWidgetPrivate { public: CppCodeStylePreferencesWidgetPrivate(CppCodeStylePreferencesWidget *widget) : q(widget) , m_indentAccessSpecifiers(createCheckBox(Tr::tr("\"public\", \"protected\" and\n" "\"private\" within class body"))) , m_indentDeclarationsRelativeToAccessSpecifiers( createCheckBox(Tr::tr("Declarations relative to \"public\",\n" "\"protected\" and \"private\""))) , m_indentFunctionBody(createCheckBox(Tr::tr("Statements within function body"))) , m_indentBlockBody(createCheckBox(Tr::tr("Statements within blocks"))) , m_indentNamespaceBody(createCheckBox(Tr::tr("Declarations within\n" "\"namespace\" definition"))) , m_indentClassBraces(createCheckBox(Tr::tr("Class declarations"))) , m_indentNamespaceBraces(createCheckBox(Tr::tr("Namespace declarations"))) , m_indentEnumBraces(createCheckBox(Tr::tr("Enum declarations"))) , m_indentFunctionBraces(createCheckBox(Tr::tr("Function declarations"))) , m_indentBlockBraces(createCheckBox(Tr::tr("Blocks"))) , m_indentSwitchLabels(createCheckBox(Tr::tr("\"case\" or \"default\""))) , m_indentCaseStatements(createCheckBox(Tr::tr("Statements relative to\n" "\"case\" or \"default\""))) , m_indentCaseBlocks(createCheckBox(Tr::tr("Blocks relative to\n" "\"case\" or \"default\""))) , m_indentCaseBreak(createCheckBox(Tr::tr("\"break\" statement relative to\n" "\"case\" or \"default\""))) , m_alignAssignments(createCheckBox( Tr::tr("Align after assignments"), Tr::tr("\n" "Enables alignment to tokens after =, += etc. When the option is disabled, " "regular continuation line indentation will be used.
\n" "
\n" "With alignment:\n" "
\n"
                   "a = a +\n"
                   "    b\n"
                   "
\n" "Without alignment:\n" "
\n"
                   "a = a +\n"
                   "        b\n"
                   "
\n" ""))) , m_extraPaddingConditions(createCheckBox( Tr::tr("Add extra padding to conditions\n" "if they would align to the next line"), Tr::tr("\n" "Adds an extra level of indentation to multiline conditions in the switch, " "if, while and foreach statements if they would otherwise have the same or " "less indentation than a nested statement.\n" "\n" "For four-spaces indentation only if statement conditions are affected. " "Without extra padding:\n" "
\n"
                   "if (a &&\n"
                   "    b)\n"
                   "    c;\n"
                   "
\n" "With extra padding:\n" "
\n"
                   "if (a &&\n"
                   "        b)\n"
                   "    c;\n"
                   "
\n" ""))) , m_bindStarToIdentifier(createCheckBox( Tr::tr("Identifier"), Tr::tr("This does not apply to the star and reference symbol " "in pointer/reference to functions and arrays, e.g.:\n" "
   int (&rf)() = ...;\n"
               "   int (*pf)() = ...;\n"
               "\n"
               "   int (&ra)[2] = ...;\n"
               "   int (*pa)[2] = ...;\n"
               "\n"
               "
"))) , m_bindStarToTypeName(createCheckBox(Tr::tr("Type name"))) , m_bindStarToLeftSpecifier(createCheckBox(Tr::tr("Left const/volatile"))) , m_bindStarToRightSpecifier(createCheckBox(Tr::tr("Right const/volatile"), Tr::tr("This does not apply to references."))) , m_tabSettingsWidget(new TabSettingsWidget) , m_statementMacros(new QPlainTextEdit) { QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); sizePolicy.setHorizontalStretch(0); sizePolicy.setVerticalStretch(0); sizePolicy.setHeightForWidth(m_tabSettingsWidget->sizePolicy().hasHeightForWidth()); m_tabSettingsWidget->setSizePolicy(sizePolicy); m_tabSettingsWidget->setFocusPolicy(Qt::TabFocus); QObject::connect(m_tabSettingsWidget, &TabSettingsWidget::settingsChanged, q, &CppCodeStylePreferencesWidget::slotTabSettingsChanged); using namespace Layouting; QWidget *contentGroupWidget = nullptr; QWidget *bracesGroupWidget = nullptr; QWidget *switchGroupWidget = nullptr; QWidget *alignmentGroupWidget = nullptr; QWidget *typesGroupWidget = nullptr; const Group contentGroup { title(Tr::tr("Indent")), bindTo(&contentGroupWidget), Column { m_indentAccessSpecifiers, m_indentDeclarationsRelativeToAccessSpecifiers, m_indentFunctionBody, m_indentBlockBody, m_indentNamespaceBody, st } }; const Group bracesGroup { title(Tr::tr("Indent Braces")), bindTo(&bracesGroupWidget), Column { m_indentClassBraces, m_indentNamespaceBraces, m_indentEnumBraces, m_indentFunctionBraces, m_indentBlockBraces, st } }; const Group switchGroup { title(Tr::tr("Indent within \"switch\"")), bindTo(&switchGroupWidget), Column { m_indentSwitchLabels, m_indentCaseStatements, m_indentCaseBlocks, m_indentCaseBreak, st } }; const Group alignmentGroup { title(Tr::tr("Align")), bindTo(&alignmentGroupWidget), Column { m_alignAssignments, m_extraPaddingConditions, st } }; const Group typesGroup { title(Tr::tr("Bind '*' and '&&' in types/declarations to")), bindTo(&typesGroupWidget), Column { m_bindStarToIdentifier, m_bindStarToTypeName, m_bindStarToLeftSpecifier, m_bindStarToRightSpecifier, st } }; sizePolicy.setVerticalPolicy(QSizePolicy::Preferred); m_statementMacros->setToolTip( Tr::tr("Macros that can be used as statements without a trailing semicolon")); m_statementMacros->setSizePolicy(sizePolicy); const Group statementMacrosGroup { title(Tr::tr("Statement macros")), Column { m_statementMacros} }; QObject::connect(m_statementMacros, &QPlainTextEdit::textChanged, q, [this] { m_handlingStatementMacroChange = true; q->slotCodeStyleSettingsChanged(); m_handlingStatementMacroChange = false; }); Row { TabWidget { bindTo(&m_categoryTab), Tab { Tr::tr("General"), Row { Column { m_tabSettingsWidget, statementMacrosGroup }, createPreview(0) } }, Tab { Tr::tr("Content"), Row { contentGroup, createPreview(1) } }, Tab { Tr::tr("Braces"), Row { bracesGroup, createPreview(2) } }, Tab { Tr::tr("\"switch\""), Row { switchGroup, createPreview(3) } }, Tab { Tr::tr("Alignment"), Row { alignmentGroup, createPreview(4) } }, Tab { Tr::tr("Pointers and References"), Row { typesGroup, createPreview(5) } } } }.attachTo(q); m_categoryTab->setProperty("_q_custom_style_disabled", true); m_controllers.append(m_tabSettingsWidget); m_controllers.append(contentGroupWidget); m_controllers.append(bracesGroupWidget); m_controllers.append(switchGroupWidget); m_controllers.append(alignmentGroupWidget); m_controllers.append(typesGroupWidget); } QCheckBox *createCheckBox(const QString &text, const QString &toolTip = {}) { QCheckBox *checkBox = new QCheckBox(text); checkBox->setToolTip(toolTip); QObject::connect(checkBox, &QCheckBox::toggled, q, &CppCodeStylePreferencesWidget::slotCodeStyleSettingsChanged); return checkBox; } SnippetEditorWidget *createPreview(int i) { SnippetEditorWidget *editor = new SnippetEditorWidget; editor->setPlainText(QLatin1String(Constants::DEFAULT_CODE_STYLE_SNIPPETS[i])); m_previews.append(editor); return editor; } CppCodeStylePreferencesWidget *q = nullptr; QCheckBox *m_indentAccessSpecifiers = nullptr; QCheckBox *m_indentDeclarationsRelativeToAccessSpecifiers = nullptr; QCheckBox *m_indentFunctionBody = nullptr; QCheckBox *m_indentBlockBody = nullptr; QCheckBox *m_indentNamespaceBody = nullptr; QCheckBox *m_indentClassBraces = nullptr; QCheckBox *m_indentNamespaceBraces = nullptr; QCheckBox *m_indentEnumBraces = nullptr; QCheckBox *m_indentFunctionBraces = nullptr; QCheckBox *m_indentBlockBraces = nullptr; QCheckBox *m_indentSwitchLabels = nullptr; QCheckBox *m_indentCaseStatements = nullptr; QCheckBox *m_indentCaseBlocks = nullptr; QCheckBox *m_indentCaseBreak = nullptr; QCheckBox *m_alignAssignments = nullptr; QCheckBox *m_extraPaddingConditions = nullptr; QCheckBox *m_bindStarToIdentifier = nullptr; QCheckBox *m_bindStarToTypeName = nullptr; QCheckBox *m_bindStarToLeftSpecifier = nullptr; QCheckBox *m_bindStarToRightSpecifier = nullptr; QList m_previews; QList m_controllers; QTabWidget *m_categoryTab = nullptr; TabSettingsWidget *m_tabSettingsWidget = nullptr; QPlainTextEdit * const m_statementMacros; bool m_handlingStatementMacroChange = false; }; CppCodeStylePreferencesWidget::CppCodeStylePreferencesWidget(QWidget *parent) : TextEditor::CodeStyleEditorWidget(parent), d(new CppCodeStylePreferencesWidgetPrivate(this)) { decorateEditors(TextEditorSettings::fontSettings()); connect(TextEditorSettings::instance(), &TextEditorSettings::fontSettingsChanged, this, &CppCodeStylePreferencesWidget::decorateEditors); setVisualizeWhitespace(true); // m_ui->categoryTab->setCurrentIndex(0); } CppCodeStylePreferencesWidget::~CppCodeStylePreferencesWidget() { delete d; } void CppCodeStylePreferencesWidget::setCodeStyle(CppCodeStylePreferences *codeStylePreferences) { // code preferences m_preferences = codeStylePreferences; connect(m_preferences, &CppCodeStylePreferences::currentTabSettingsChanged, this, &CppCodeStylePreferencesWidget::setTabSettings); connect(m_preferences, &CppCodeStylePreferences::currentCodeStyleSettingsChanged, this, [this](const CppCodeStyleSettings &codeStyleSettings) { setCodeStyleSettings(codeStyleSettings); }); connect(m_preferences, &ICodeStylePreferences::currentPreferencesChanged, this, [this](TextEditor::ICodeStylePreferences *currentPreferences) { slotCurrentPreferencesChanged(currentPreferences); }); setTabSettings(m_preferences->currentTabSettings()); setCodeStyleSettings(m_preferences->currentCodeStyleSettings(), false); slotCurrentPreferencesChanged(m_preferences->currentPreferences(), false); m_originalCppCodeStyleSettings = cppCodeStyleSettings(); m_originalTabSettings = tabSettings(); updatePreview(); } CppCodeStyleSettings CppCodeStylePreferencesWidget::cppCodeStyleSettings() const { CppCodeStyleSettings set; set.statementMacros = Utils::transform(d->m_statementMacros->toPlainText().trimmed().split('\n', Qt::SkipEmptyParts), [](const QString &line) { return line.trimmed(); }); set.indentBlockBraces = d->m_indentBlockBraces->isChecked(); set.indentBlockBody = d->m_indentBlockBody->isChecked(); set.indentClassBraces = d->m_indentClassBraces->isChecked(); set.indentEnumBraces = d->m_indentEnumBraces->isChecked(); set.indentNamespaceBraces = d->m_indentNamespaceBraces->isChecked(); set.indentNamespaceBody = d->m_indentNamespaceBody->isChecked(); set.indentAccessSpecifiers = d->m_indentAccessSpecifiers->isChecked(); set.indentDeclarationsRelativeToAccessSpecifiers = d->m_indentDeclarationsRelativeToAccessSpecifiers->isChecked(); set.indentFunctionBody = d->m_indentFunctionBody->isChecked(); set.indentFunctionBraces = d->m_indentFunctionBraces->isChecked(); set.indentSwitchLabels = d->m_indentSwitchLabels->isChecked(); set.indentStatementsRelativeToSwitchLabels = d->m_indentCaseStatements->isChecked(); set.indentBlocksRelativeToSwitchLabels = d->m_indentCaseBlocks->isChecked(); set.indentControlFlowRelativeToSwitchLabels = d->m_indentCaseBreak->isChecked(); set.bindStarToIdentifier = d->m_bindStarToIdentifier->isChecked(); set.bindStarToTypeName = d->m_bindStarToTypeName->isChecked(); set.bindStarToLeftSpecifier = d->m_bindStarToLeftSpecifier->isChecked(); set.bindStarToRightSpecifier = d->m_bindStarToRightSpecifier->isChecked(); set.extraPaddingForConditionsIfConfusingAlign = d->m_extraPaddingConditions->isChecked(); set.alignAssignments = d->m_alignAssignments->isChecked(); return set; } void CppCodeStylePreferencesWidget::setTabSettings(const TabSettings &settings) { d->m_tabSettingsWidget->setTabSettings(settings); } TextEditor::TabSettings CppCodeStylePreferencesWidget::tabSettings() const { return d->m_tabSettingsWidget->tabSettings(); } void CppCodeStylePreferencesWidget::setCodeStyleSettings(const CppCodeStyleSettings &s, bool preview) { const bool wasBlocked = m_blockUpdates; m_blockUpdates = true; if (!d->m_handlingStatementMacroChange) d->m_statementMacros->setPlainText(s.statementMacros.join('\n')); d->m_indentBlockBraces->setChecked(s.indentBlockBraces); d->m_indentBlockBody->setChecked(s.indentBlockBody); d->m_indentClassBraces->setChecked(s.indentClassBraces); d->m_indentEnumBraces->setChecked(s.indentEnumBraces); d->m_indentNamespaceBraces->setChecked(s.indentNamespaceBraces); d->m_indentNamespaceBody->setChecked(s.indentNamespaceBody); d->m_indentAccessSpecifiers->setChecked(s.indentAccessSpecifiers); d->m_indentDeclarationsRelativeToAccessSpecifiers->setChecked(s.indentDeclarationsRelativeToAccessSpecifiers); d->m_indentFunctionBody->setChecked(s.indentFunctionBody); d->m_indentFunctionBraces->setChecked(s.indentFunctionBraces); d->m_indentSwitchLabels->setChecked(s.indentSwitchLabels); d->m_indentCaseStatements->setChecked(s.indentStatementsRelativeToSwitchLabels); d->m_indentCaseBlocks->setChecked(s.indentBlocksRelativeToSwitchLabels); d->m_indentCaseBreak->setChecked(s.indentControlFlowRelativeToSwitchLabels); d->m_bindStarToIdentifier->setChecked(s.bindStarToIdentifier); d->m_bindStarToTypeName->setChecked(s.bindStarToTypeName); d->m_bindStarToLeftSpecifier->setChecked(s.bindStarToLeftSpecifier); d->m_bindStarToRightSpecifier->setChecked(s.bindStarToRightSpecifier); d->m_extraPaddingConditions->setChecked(s.extraPaddingForConditionsIfConfusingAlign); d->m_alignAssignments->setChecked(s.alignAssignments); m_blockUpdates = wasBlocked; if (preview) updatePreview(); } void CppCodeStylePreferencesWidget::slotCurrentPreferencesChanged(ICodeStylePreferences *preferences, bool preview) { const bool enable = !preferences->isReadOnly() && (!preferences->isTemporarilyReadOnly() || !preferences->isAdditionalTabVisible()); if (preferences->additionalTabExist()) { d->m_categoryTab->setTabVisible(0, preferences->isAdditionalTabVisible()); for (int i = 1; i < d->m_categoryTab->count(); ++i) d->m_categoryTab->setTabVisible(i, !preferences->isAdditionalTabVisible()); } for (QWidget *widget : d->m_controllers) widget->setEnabled(enable); if (preview) updatePreview(); } void CppCodeStylePreferencesWidget::slotCodeStyleSettingsChanged() { if (m_blockUpdates) return; if (m_preferences) { auto current = qobject_cast(m_preferences->currentPreferences()); if (current) current->setCodeStyleSettings(cppCodeStyleSettings()); } emit codeStyleSettingsChanged(cppCodeStyleSettings()); updatePreview(); } void CppCodeStylePreferencesWidget::slotTabSettingsChanged(const TabSettings &settings) { if (m_blockUpdates) return; if (m_preferences) { auto current = qobject_cast(m_preferences->currentPreferences()); if (current) current->setTabSettings(settings); } emit tabSettingsChanged(settings); updatePreview(); } void CppCodeStylePreferencesWidget::updatePreview() { CppCodeStylePreferences *cppCodeStylePreferences = m_preferences ? m_preferences : CppToolsSettings::cppCodeStyle(); const CppCodeStyleSettings ccss = cppCodeStylePreferences->currentCodeStyleSettings(); const TabSettings ts = cppCodeStylePreferences->currentTabSettings(); QtStyleCodeFormatter formatter(ts, ccss); for (SnippetEditorWidget *preview : std::as_const(d->m_previews)) { preview->textDocument()->setTabSettings(ts); preview->setCodeStyle(cppCodeStylePreferences); QTextDocument *doc = preview->document(); formatter.invalidateCache(doc); QTextBlock block = doc->firstBlock(); QTextCursor tc = preview->textCursor(); tc.beginEditBlock(); while (block.isValid()) { preview->textDocument()->indenter()->indentBlock(block, QChar::Null, ts); block = block.next(); } applyRefactorings(doc, preview, ccss); tc.endEditBlock(); } } void CppCodeStylePreferencesWidget::decorateEditors(const FontSettings &fontSettings) { for (SnippetEditorWidget *editor : std::as_const(d->m_previews)) { editor->textDocument()->setFontSettings(fontSettings); SnippetProvider::decorateEditor(editor, CppEditor::Constants::CPP_SNIPPETS_GROUP_ID); } } void CppCodeStylePreferencesWidget::setVisualizeWhitespace(bool on) { for (SnippetEditorWidget *editor : std::as_const(d->m_previews)) { DisplaySettings displaySettings = editor->displaySettings(); displaySettings.m_visualizeWhitespace = on; editor->setDisplaySettings(displaySettings); } } void CppCodeStylePreferencesWidget::addTab(TextEditor::CodeStyleEditorWidget *page, QString tabName) { if (!page) return; d->m_categoryTab->insertTab(0, page, tabName); d->m_categoryTab->setCurrentIndex(0); connect(this, &CppCodeStylePreferencesWidget::applyEmitted, page, &TextEditor::CodeStyleEditorWidget::apply); connect(this, &CppCodeStylePreferencesWidget::finishEmitted, page, &TextEditor::CodeStyleEditorWidget::finish); slotCurrentPreferencesChanged(m_preferences->currentPreferences(), false); } void CppCodeStylePreferencesWidget::apply() { m_originalTabSettings = tabSettings(); m_originalCppCodeStyleSettings = cppCodeStyleSettings(); emit applyEmitted(); } void CppCodeStylePreferencesWidget::finish() { if (m_preferences) { auto current = qobject_cast(m_preferences->currentDelegate()); if (current) { current->setCodeStyleSettings(m_originalCppCodeStyleSettings); current->setTabSettings(m_originalTabSettings); } } emit finishEmitted(); } // CppCodeStyleSettingsPageWidget class CppCodeStyleSettingsPageWidget : public Core::IOptionsPageWidget { public: CppCodeStyleSettingsPageWidget() { CppCodeStylePreferences *originalCodeStylePreferences = CppToolsSettings::cppCodeStyle(); m_pageCppCodeStylePreferences.reset(new CppCodeStylePreferences); m_pageCppCodeStylePreferences->setDelegatingPool( originalCodeStylePreferences->delegatingPool()); m_pageCppCodeStylePreferences->setCodeStyleSettings( originalCodeStylePreferences->codeStyleSettings()); m_pageCppCodeStylePreferences->setCurrentDelegate( originalCodeStylePreferences->currentDelegate()); // we set id so that it won't be possible to set delegate to the original prefs m_pageCppCodeStylePreferences->setId(originalCodeStylePreferences->id()); m_codeStyleEditor = TextEditorSettings::codeStyleFactory(CppEditor::Constants::CPP_SETTINGS_ID) ->createCodeStyleEditor(m_pageCppCodeStylePreferences.get()); auto hbox = new QVBoxLayout(this); hbox->addWidget(m_codeStyleEditor); } void apply() final { CppCodeStylePreferences *originalCppCodeStylePreferences = CppToolsSettings::cppCodeStyle(); if (originalCppCodeStylePreferences->codeStyleSettings() != m_pageCppCodeStylePreferences->codeStyleSettings()) { originalCppCodeStylePreferences->setCodeStyleSettings(m_pageCppCodeStylePreferences->codeStyleSettings()); originalCppCodeStylePreferences->toSettings(CppEditor::Constants::CPP_SETTINGS_ID); } if (originalCppCodeStylePreferences->tabSettings() != m_pageCppCodeStylePreferences->tabSettings()) { originalCppCodeStylePreferences->setTabSettings(m_pageCppCodeStylePreferences->tabSettings()); originalCppCodeStylePreferences->toSettings(CppEditor::Constants::CPP_SETTINGS_ID); } if (originalCppCodeStylePreferences->currentDelegate() != m_pageCppCodeStylePreferences->currentDelegate()) { originalCppCodeStylePreferences->setCurrentDelegate(m_pageCppCodeStylePreferences->currentDelegate()); originalCppCodeStylePreferences->toSettings(CppEditor::Constants::CPP_SETTINGS_ID); } m_codeStyleEditor->apply(); } void finish() final { m_codeStyleEditor->finish(); } std::unique_ptr m_pageCppCodeStylePreferences; CodeStyleEditorWidget *m_codeStyleEditor; }; // CppCodeStyleSettingsPage class CppCodeStyleSettingsPage : public Core::IOptionsPage { public: CppCodeStyleSettingsPage() { setId(Constants::CPP_CODE_STYLE_SETTINGS_ID); setDisplayName(Tr::tr("Code Style")); setCategory(Constants::CPP_SETTINGS_CATEGORY); setWidgetCreator([] { return new CppCodeStyleSettingsPageWidget; }); } }; void setupCppCodeStyleSettings() { static CppCodeStyleSettingsPage theCppCodeStyleSettingsPage; } } // namespace CppEditor::Internal