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,