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 <christian.stenger@qt.io>
This commit is contained in:
David Schulz
2024-11-27 15:13:04 +01:00
parent 6cdacdacef
commit 08a66b7780
11 changed files with 58 additions and 89 deletions

View File

@@ -78,14 +78,11 @@ std::optional<TabSettings> 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<int>(style.TabWidth);

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -9,7 +9,7 @@
#include <QTextDocument>
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<int, int> 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<bool> {
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;

View File

@@ -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 &currentBlock = 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;

View File

@@ -7,6 +7,7 @@
#include "texteditortr.h"
#include <QApplication>
#include <QCheckBox>
#include <QComboBox>
#include <QLabel>
#include <QSpinBox>
@@ -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);

View File

@@ -8,6 +8,7 @@
#include <QGroupBox>
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;

View File

@@ -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));
}
}
}

View File

@@ -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<void(TabSettings & tabSettings)> 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);

View File

@@ -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<TabSettings::TabPolicy> allPolicies = {
TabSettings::SpacesOnlyTabPolicy,
TabSettings::TabsOnlyTabPolicy,
TabSettings::MixedTabPolicy
};
const QVector<TabSettings::ContinuationAlignBehavior> allbehaviors = {
TabSettings::NoContinuationAlign,