From 08a66b778064ece5f01ee3323d8a5b5171b02c09 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Wed, 27 Nov 2024 15:13:04 +0100 Subject: [PATCH] Editor: use detected indentation by default This removes the undescribed Mixed tab settings option. It was unclear and undocumented what this option actually means. In theory this option tries to detect whether tabs or spaces are used for indentation around a specific position in the document. This now conflicts with the automatic auto detection which scans the complete document. That auto detection not just detects whether tabs are used but also the indent depth. Having a source document with a different indent depth is arguably a more common use case than a source file with mixed tabs and spaces for indentation. So in order to not confuse the user with to many unclear magic options that might interfere with each other the mixed tab settings option was removed in this change. Fixes: QTCREATORBUG-11575 Fixes: QTCREATORBUG-11675 Fixes: QTCREATORBUG-19576 Fixes: QTCREATORBUG-25628 Change-Id: Ib95662ade38d0384d503e9a7b99f54ea4b416f68 Reviewed-by: Christian Stenger --- .../clangformat/clangformatindenter.cpp | 5 +- src/plugins/clangformat/clangformatutils.cpp | 7 +- src/plugins/cppeditor/cpptoolssettings.cpp | 4 +- .../cppeditor/quickfixes/rewritecomment.cpp | 2 +- src/plugins/texteditor/tabsettings.cpp | 69 ++++++------------- src/plugins/texteditor/tabsettings.h | 8 +-- src/plugins/texteditor/tabsettingswidget.cpp | 19 +++-- src/plugins/texteditor/tabsettingswidget.h | 2 + src/plugins/texteditor/textdocument.cpp | 20 +++--- src/plugins/texteditor/texteditor.cpp | 8 +-- src/plugins/texteditor/texteditor_test.cpp | 3 - 11 files changed, 58 insertions(+), 89 deletions(-) diff --git a/src/plugins/clangformat/clangformatindenter.cpp b/src/plugins/clangformat/clangformatindenter.cpp index 5f30c77ebf9..21278e136aa 100644 --- a/src/plugins/clangformat/clangformatindenter.cpp +++ b/src/plugins/clangformat/clangformatindenter.cpp @@ -78,14 +78,11 @@ std::optional ClangFormatIndenter::tabSettings() const TabSettings tabSettings; switch (style.UseTab) { - case FormatStyle::UT_Never: - tabSettings.m_tabPolicy = TabSettings::SpacesOnlyTabPolicy; - break; case FormatStyle::UT_Always: tabSettings.m_tabPolicy = TabSettings::TabsOnlyTabPolicy; break; default: - tabSettings.m_tabPolicy = TabSettings::MixedTabPolicy; + tabSettings.m_tabPolicy = TabSettings::SpacesOnlyTabPolicy; } tabSettings.m_tabSize = static_cast(style.TabWidth); diff --git a/src/plugins/clangformat/clangformatutils.cpp b/src/plugins/clangformat/clangformatutils.cpp index dcc7cbc84c8..6a439e4138f 100644 --- a/src/plugins/clangformat/clangformatutils.cpp +++ b/src/plugins/clangformat/clangformatutils.cpp @@ -246,13 +246,10 @@ void fromTabSettings(clang::format::FormatStyle &style, const TextEditor::TabSet style.TabWidth = settings.m_tabSize; switch (settings.m_tabPolicy) { - case TextEditor::TabSettings::TabPolicy::MixedTabPolicy: - style.UseTab = FormatStyle::UT_ForContinuationAndIndentation; - break; - case TextEditor::TabSettings::TabPolicy::SpacesOnlyTabPolicy: + case TextEditor::TabSettings::SpacesOnlyTabPolicy: style.UseTab = FormatStyle::UT_Never; break; - case TextEditor::TabSettings::TabPolicy::TabsOnlyTabPolicy: + case TextEditor::TabSettings::TabsOnlyTabPolicy: style.UseTab = FormatStyle::UT_Always; break; } diff --git a/src/plugins/cppeditor/cpptoolssettings.cpp b/src/plugins/cppeditor/cpptoolssettings.cpp index 284b36d232a..abf2626bf33 100644 --- a/src/plugins/cppeditor/cpptoolssettings.cpp +++ b/src/plugins/cppeditor/cpptoolssettings.cpp @@ -106,8 +106,8 @@ CppToolsSettings::CppToolsSettings() gnuCodeStyle->setDisplayName(Tr::tr("GNU")); gnuCodeStyle->setReadOnly(true); TabSettings gnuTabSettings; - gnuTabSettings.m_tabPolicy = TabSettings::MixedTabPolicy; - gnuTabSettings.m_tabSize = 8; + gnuTabSettings.m_tabPolicy = TabSettings::TabsOnlyTabPolicy; + gnuTabSettings.m_tabSize = 2; gnuTabSettings.m_indentSize = 2; gnuTabSettings.m_continuationAlignBehavior = TabSettings::ContinuationAlignWithIndent; gnuCodeStyle->setTabSettings(gnuTabSettings); diff --git a/src/plugins/cppeditor/quickfixes/rewritecomment.cpp b/src/plugins/cppeditor/quickfixes/rewritecomment.cpp index 3ac7fede2c5..a7bea4c0471 100644 --- a/src/plugins/cppeditor/quickfixes/rewritecomment.cpp +++ b/src/plugins/cppeditor/quickfixes/rewritecomment.cpp @@ -324,7 +324,7 @@ private: int lineIndentColumn = sts.indentationColumn(text) + columnOffset; text.replace(0, TabSettings::firstNonSpace(text), - tts.indentationString(0, lineIndentColumn, 0, insertionBlock)); + tts.indentationString(0, lineIndentColumn, 0)); } functionDoc += text; } diff --git a/src/plugins/texteditor/tabsettings.cpp b/src/plugins/texteditor/tabsettings.cpp index 54aa18f3c29..debc9e199ac 100644 --- a/src/plugins/texteditor/tabsettings.cpp +++ b/src/plugins/texteditor/tabsettings.cpp @@ -9,7 +9,7 @@ #include static const char spacesForTabsKey[] = "SpacesForTabs"; -static const char autoSpacesForTabsKey[] = "AutoSpacesForTabs"; +static const char autoDetectKey[] = "AutoDetect"; static const char tabSizeKey[] = "TabSize"; static const char indentSizeKey[] = "IndentSize"; static const char paddingModeKey[] = "PaddingMode"; @@ -33,7 +33,7 @@ Store TabSettings::toMap() const { return { {spacesForTabsKey, m_tabPolicy != TabsOnlyTabPolicy}, - {autoSpacesForTabsKey, m_tabPolicy == MixedTabPolicy}, + {autoDetectKey, m_autoDetect}, {tabSizeKey, m_tabSize}, {indentSizeKey, m_indentSize}, {paddingModeKey, m_continuationAlignBehavior} @@ -43,8 +43,8 @@ Store TabSettings::toMap() const void TabSettings::fromMap(const Store &map) { const bool spacesForTabs = map.value(spacesForTabsKey, true).toBool(); - const bool autoSpacesForTabs = map.value(autoSpacesForTabsKey, false).toBool(); - m_tabPolicy = spacesForTabs ? (autoSpacesForTabs ? MixedTabPolicy : SpacesOnlyTabPolicy) : TabsOnlyTabPolicy; + m_autoDetect = map.value(autoDetectKey, true).toBool(); + m_tabPolicy = spacesForTabs ? SpacesOnlyTabPolicy : TabsOnlyTabPolicy; m_tabSize = map.value(tabSizeKey, m_tabSize).toInt(); m_indentSize = map.value(indentSizeKey, m_indentSize).toInt(); m_continuationAlignBehavior = (ContinuationAlignBehavior) @@ -55,8 +55,7 @@ TabSettings TabSettings::autoDetect(const QTextDocument *document) const { QTC_ASSERT(document, return *this); - const int blockCount = document->blockCount(); - if (blockCount < 10) + if (!m_autoDetect) return *this; int totalIndentations = 0; @@ -64,16 +63,15 @@ TabSettings TabSettings::autoDetect(const QTextDocument *document) const QMap indentCount; auto checkText = - [this, &totalIndentations, &indentCount, &indentationWithTabs](const QTextBlock &block) { + [this, document, &totalIndentations, &indentCount, &indentationWithTabs](const QTextBlock &block) { if (block.length() == 0) return; - const QTextDocument *doc = block.document(); int pos = block.position(); bool hasTabs = false; int indentation = 0; // iterate ove the characters in the document is faster since we do not have to allocate // a string for each block text when we are only interested in the first few characters - QChar c = doc->characterAt(pos); + QChar c = document->characterAt(pos); while (c.isSpace() && c != QChar::ParagraphSeparator) { if (c == QChar::Tabulation) { hasTabs = true; @@ -81,7 +79,7 @@ TabSettings TabSettings::autoDetect(const QTextDocument *document) const } else { ++indentation; } - c = doc->characterAt(++pos); + c = document->characterAt(++pos); } // only track indentations that are at least 2 columns wide if (indentation > 1) { @@ -92,6 +90,7 @@ TabSettings TabSettings::autoDetect(const QTextDocument *document) const } }; + const int blockCount = document->blockCount(); if (blockCount < 200) { // check the indentation of all blocks if the document is shorter than 200 lines for (QTextBlock block = document->firstBlock(); block.isValid(); block = block.next()) @@ -118,6 +117,9 @@ TabSettings TabSettings::autoDetect(const QTextDocument *document) const } } + if (indentCount.size() < 3) + return *this; + // find the most common indent int mostCommonIndent = 0; int mostCommonIndentCount = 0; @@ -222,7 +224,6 @@ bool TabSettings::isIndentationClean(const QTextBlock &block, const int indent) int i = 0; int spaceCount = 0; QString text = block.text(); - bool spacesForTabs = guessSpacesForTabs(block); while (i < text.size()) { QChar c = text.at(i); if (!c.isSpace()) @@ -231,13 +232,13 @@ bool TabSettings::isIndentationClean(const QTextBlock &block, const int indent) if (c == QLatin1Char(' ')) { ++spaceCount; if (spaceCount == m_tabSize) - if (!spacesForTabs) + if (m_tabPolicy == TabsOnlyTabPolicy) if ((m_continuationAlignBehavior != ContinuationAlignWithSpaces) || (i < indent)) return false; if (spaceCount > indent && m_continuationAlignBehavior == NoContinuationAlign) return false; } else if (c == QLatin1Char('\t')) { - if (spacesForTabs || (spaceCount != 0)) + if (m_tabPolicy == SpacesOnlyTabPolicy || (spaceCount != 0)) return false; if ((m_continuationAlignBehavior != ContinuationAlignWithIndent) && ((i + 1) * m_tabSize > indent)) return false; @@ -316,41 +317,10 @@ int TabSettings::indentedColumn(int column, bool doIndent) const return qMax(0, aligned - m_indentSize); } -bool TabSettings::guessSpacesForTabs(const QTextBlock &block) const -{ - if (m_tabPolicy == MixedTabPolicy && block.isValid()) { - QTextBlock prev = block.previous(); - QTextBlock next = block.next(); - - auto checkFirstChar = - [doc = block.document()](const QTextBlock &block) -> std::optional { - if (block.length() > 0) { - const QChar firstChar = doc->characterAt(block.position()); - if (firstChar == QLatin1Char(' ')) - return true; - if (firstChar == QLatin1Char('\t')) - return false; - } - return {}; - }; - - for (int delta = 1; delta <= 100 && (prev.isValid() || next.isValid()); ++delta) { - if (auto result = checkFirstChar(prev)) - return *result; - if (auto result = checkFirstChar(next)) - return *result; - prev = prev.previous(); - next = next.next(); - } - } - return m_tabPolicy != TabsOnlyTabPolicy; -} - -QString TabSettings::indentationString(int startColumn, int targetColumn, int padding, - const QTextBlock &block) const +QString TabSettings::indentationString(int startColumn, int targetColumn, int padding) const { targetColumn = qMax(startColumn, targetColumn); - if (guessSpacesForTabs(block)) + if (m_tabPolicy == SpacesOnlyTabPolicy) return QString(targetColumn - startColumn, QLatin1Char(' ')); QString s; @@ -390,7 +360,7 @@ void TabSettings::indentLine(const QTextBlock &block, int newIndent, int padding // if (indentationColumn(text) == newIndent) // return; - const QString indentString = indentationString(0, newIndent, padding, block); + const QString indentString = indentationString(0, newIndent, padding); if (oldBlockLength == indentString.length() && text == indentString) return; @@ -419,7 +389,7 @@ void TabSettings::reindentLine(QTextBlock block, int delta) const // user likes tabs for spaces and uses tabs for indentation, preserve padding if (m_tabPolicy == TabsOnlyTabPolicy && m_tabSize == m_indentSize) padding = qMin(maximumPadding(text), newIndent); - const QString indentString = indentationString(0, newIndent, padding, block); + const QString indentString = indentationString(0, newIndent, padding); if (oldBlockLength == indentString.length() && text == indentString) return; @@ -434,7 +404,8 @@ void TabSettings::reindentLine(QTextBlock block, int delta) const bool TabSettings::equals(const TabSettings &ts) const { - return m_tabPolicy == ts.m_tabPolicy + return m_autoDetect == ts.m_autoDetect + && m_tabPolicy == ts.m_tabPolicy && m_tabSize == ts.m_tabSize && m_indentSize == ts.m_indentSize && m_continuationAlignBehavior == ts.m_continuationAlignBehavior; diff --git a/src/plugins/texteditor/tabsettings.h b/src/plugins/texteditor/tabsettings.h index 4d28a3c05cd..f173cd0e68c 100644 --- a/src/plugins/texteditor/tabsettings.h +++ b/src/plugins/texteditor/tabsettings.h @@ -20,11 +20,9 @@ namespace TextEditor { class TEXTEDITORSUPPORT_EXPORT TabSettings { public: - enum TabPolicy { SpacesOnlyTabPolicy = 0, - TabsOnlyTabPolicy = 1, - MixedTabPolicy = 2 + TabsOnlyTabPolicy }; // This enum must match the indexes of continuationAlignBehavior widget @@ -49,7 +47,7 @@ public: int positionAtColumn(const QString &text, int column, int *offset = nullptr, bool allowOverstep = false) const; int columnCountForText(const QString &text, int startColumn = 0) const; int indentedColumn(int column, bool doIndent = true) const; - QString indentationString(int startColumn, int targetColumn, int padding, const QTextBlock ¤tBlock = QTextBlock()) const; + QString indentationString(int startColumn, int targetColumn, int padding) const; int indentationColumn(const QString &text) const; static int maximumPadding(const QString &text); @@ -57,7 +55,6 @@ public: void reindentLine(QTextBlock block, int delta) const; bool isIndentationClean(const QTextBlock &block, const int indent) const; - bool guessSpacesForTabs(const QTextBlock &block) const; friend bool operator==(const TabSettings &t1, const TabSettings &t2) { return t1.equals(t2); } friend bool operator!=(const TabSettings &t1, const TabSettings &t2) { return !t1.equals(t2); } @@ -70,6 +67,7 @@ public: static int trailingWhitespaces(const QString &text); static void removeTrailingWhitespace(QTextCursor cursor, QTextBlock &block); + bool m_autoDetect = true; TabPolicy m_tabPolicy = SpacesOnlyTabPolicy; int m_tabSize = 8; int m_indentSize = 4; diff --git a/src/plugins/texteditor/tabsettingswidget.cpp b/src/plugins/texteditor/tabsettingswidget.cpp index c796591ffc6..fb48fa7a6b8 100644 --- a/src/plugins/texteditor/tabsettingswidget.cpp +++ b/src/plugins/texteditor/tabsettingswidget.cpp @@ -7,6 +7,7 @@ #include "texteditortr.h" #include +#include #include #include #include @@ -60,17 +61,22 @@ TabSettingsWidget::TabSettingsWidget(QWidget *parent) : Tr::tr("The text editor indentation setting is used for non-code files only. See the C++ " "and Qt Quick coding style settings to configure indentation for code files.")); + m_autoDetect = new QCheckBox(Tr::tr("Auto detect"), this); + m_autoDetect->setToolTip( + Tr::tr("%1 tries to detect the indentation settings based on the file contents. It " + "will fallback to the settings below if the detection fails.") + .arg(QGuiApplication::applicationDisplayName())); + m_tabPolicy = new QComboBox(this); m_tabPolicy->addItem(Tr::tr("Spaces Only")); m_tabPolicy->addItem(Tr::tr("Tabs Only")); - m_tabPolicy->addItem(Tr::tr("Mixed")); auto tabSizeLabel = new QLabel(Tr::tr("Ta&b size:")); m_tabSize = new QSpinBox(this); m_tabSize->setRange(1, 20); - auto indentSizeLabel = new QLabel(Tr::tr("&Indent size:")); + auto indentSizeLabel = new QLabel(Tr::tr("Default &indent size:")); m_indentSize = new QSpinBox(this); m_indentSize->setRange(1, 20); @@ -89,15 +95,18 @@ TabSettingsWidget::TabSettingsWidget(QWidget *parent) : Row { Form { m_codingStyleWarning, br, - Tr::tr("Tab policy:"), m_tabPolicy, br, - tabSizeLabel, m_tabSize, br, + m_autoDetect, br, + Tr::tr("Default tab policy:"), m_tabPolicy, br, indentSizeLabel, m_indentSize, br, + tabSizeLabel, m_tabSize, br, Tr::tr("Align continuation lines:"), m_continuationAlignBehavior, br }, st }.attachTo(this); connect(m_codingStyleWarning, &QLabel::linkActivated, this, &TabSettingsWidget::codingStyleLinkActivated); + connect(m_autoDetect, &QCheckBox::stateChanged, + this, &TabSettingsWidget::slotSettingsChanged); connect(m_tabPolicy, &QComboBox::currentIndexChanged, this, &TabSettingsWidget::slotSettingsChanged); connect(m_tabSize, &QSpinBox::valueChanged, @@ -113,7 +122,7 @@ TabSettingsWidget::~TabSettingsWidget() = default; void TabSettingsWidget::setTabSettings(const TabSettings &s) { QSignalBlocker blocker(this); - m_tabPolicy->setCurrentIndex(s.m_tabPolicy); + m_tabPolicy->setCurrentIndex(int(s.m_tabPolicy)); m_tabSize->setValue(s.m_tabSize); m_indentSize->setValue(s.m_indentSize); m_continuationAlignBehavior->setCurrentIndex(s.m_continuationAlignBehavior); diff --git a/src/plugins/texteditor/tabsettingswidget.h b/src/plugins/texteditor/tabsettingswidget.h index 802dde8b075..3de6891f750 100644 --- a/src/plugins/texteditor/tabsettingswidget.h +++ b/src/plugins/texteditor/tabsettingswidget.h @@ -8,6 +8,7 @@ #include QT_BEGIN_NAMESPACE +class QCheckBox; class QComboBox; class QLabel; class QSpinBox; @@ -44,6 +45,7 @@ private: void codingStyleLinkActivated(const QString &linkString); QLabel *m_codingStyleWarning; + QCheckBox *m_autoDetect; QComboBox *m_tabPolicy; QSpinBox *m_tabSize; QSpinBox *m_indentSize; diff --git a/src/plugins/texteditor/textdocument.cpp b/src/plugins/texteditor/textdocument.cpp index b52dafc150d..ccc0cb216bb 100644 --- a/src/plugins/texteditor/textdocument.cpp +++ b/src/plugins/texteditor/textdocument.cpp @@ -153,7 +153,7 @@ MultiTextCursor TextDocumentPrivate::indentOrUnindent(const MultiTextCursor &cur = tabSettings.indentedColumn(tabSettings.columnAt(text, indentPosition), doIndent); cursor.setPosition(block.position() + indentPosition); - cursor.insertText(tabSettings.indentationString(0, targetColumn, 0, block)); + cursor.insertText(tabSettings.indentationString(0, targetColumn, 0)); cursor.setPosition(block.position()); cursor.setPosition(block.position() + indentPosition, QTextCursor::KeepAnchor); cursor.removeSelectedText(); @@ -184,7 +184,7 @@ MultiTextCursor TextDocumentPrivate::indentOrUnindent(const MultiTextCursor &cur QTextCursor::KeepAnchor); cursor.removeSelectedText(); cursor.insertText( - tabSettings.indentationString(startColumn, targetColumn, 0, startBlock)); + tabSettings.indentationString(startColumn, targetColumn, 0)); } cursor.endEditBlock(); @@ -367,13 +367,13 @@ const StorageSettings &TextDocument::storageSettings() const return d->m_storageSettings; } -void TextDocument::setTabSettings(const TabSettings &newTabSettings) +void TextDocument::setTabSettings(const TabSettings &tabSettings) { - if (newTabSettings == d->m_tabSettings) - return; - d->m_tabSettings = newTabSettings; - - emit tabSettingsChanged(); + if (const TabSettings candidate = tabSettings.autoDetect(document()); + candidate != d->m_tabSettings) { + d->m_tabSettings = candidate; + emit tabSettingsChanged(); + } } TabSettings TextDocument::tabSettings() const @@ -752,6 +752,7 @@ Core::IDocument::OpenResult TextDocument::open(QString *errorString, OpenResult success = openImpl(errorString, filePath, realFilePath, /*reload =*/ false); if (success == OpenResult::Success) { setMimeType(Utils::mimeTypeForFile(filePath, MimeMatchMode::MatchDefaultAndRemote).name()); + setTabSettings(d->m_tabSettings); emit openFinishedSuccessfully(); } return success; @@ -960,8 +961,7 @@ void TextDocument::cleanWhitespace(QTextCursor &cursor, bool inEntireDocument, } else { int column = currentTabSettings.columnAt(blockText, firstNonSpace); cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, firstNonSpace); - QString indentationString = currentTabSettings.indentationString(0, column, column - indent, block); - cursor.insertText(indentationString); + cursor.insertText(currentTabSettings.indentationString(0, column, column - indent)); } } } diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index 5ddc6f1dcc1..1fc21b2b22e 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -302,9 +302,6 @@ private: case TabSettings::TabsOnlyTabPolicy: policy = Tr::tr("Tabs"); break; - case TabSettings::MixedTabPolicy: - policy = Tr::tr("Mixed"); - break; } setText(QString("%1: %2").arg(policy).arg(ts.m_indentSize)); } @@ -321,6 +318,7 @@ private: [this](std::function modifier) { return [this, modifier]() { auto ts = m_doc->tabSettings(); + ts.m_autoDetect = false; modifier(ts); m_doc->setTabSettings(ts); }; @@ -328,7 +326,7 @@ private: documentSettings->addAction( Tr::tr("Auto detect"), modifyTabSettings([doc = m_doc->document()](TabSettings &tabSettings) { - tabSettings = tabSettings.autoDetect(doc); + tabSettings.m_autoDetect = true; })); auto tabSettings = documentSettings->addMenu(Tr::tr("Tab Settings")); tabSettings->addAction(Tr::tr("Spaces"), modifyTabSettings([](TabSettings &tabSettings) { @@ -8999,7 +8997,7 @@ void TextEditorWidget::rewrapParagraph() QString spacing; if (commonPrefix.isEmpty()) { - spacing = ts.indentationString(0, indentLevel, 0, textCursor().block()); + spacing = ts.indentationString(0, indentLevel, 0); } else { spacing = commonPrefix; indentLevel = ts.columnCountForText(spacing); diff --git a/src/plugins/texteditor/texteditor_test.cpp b/src/plugins/texteditor/texteditor_test.cpp index c82804ee992..604770e5e1d 100644 --- a/src/plugins/texteditor/texteditor_test.cpp +++ b/src/plugins/texteditor/texteditor_test.cpp @@ -17,8 +17,6 @@ static QString tabPolicyToString(TabSettings::TabPolicy policy) return QLatin1String("spacesOnlyPolicy"); case TabSettings::TabsOnlyTabPolicy: return QLatin1String("tabsOnlyPolicy"); - case TabSettings::MixedTabPolicy: - return QLatin1String("mixedIndentPolicy"); } return QString(); } @@ -49,7 +47,6 @@ static void generateTestRows(const QLatin1String &name, const QString &text, IsC const QVector allPolicies = { TabSettings::SpacesOnlyTabPolicy, TabSettings::TabsOnlyTabPolicy, - TabSettings::MixedTabPolicy }; const QVector allbehaviors = { TabSettings::NoContinuationAlign,