diff options
author | David Schulz <david.schulz@theqtcompany.com> | 2016-01-13 14:32:23 +0100 |
---|---|---|
committer | David Schulz <david.schulz@theqtcompany.com> | 2016-02-24 12:03:12 +0000 |
commit | 9aa51d4857702c22d359014356d06c9d92677904 (patch) | |
tree | 79d73774ac0541e65fbe03e44592a43fc95ba152 /src/plugins/texteditor | |
parent | bc921b46a2946294b467e1d2bcafb147ee77361a (diff) |
Editor: Fix whitespace cleaning.
Task-number: QTCREATORBUG-7994
Change-Id: I6c197ccc3a148555018e8f8184d116c88d7ea400
Reviewed-by: Christian Stenger <christian.stenger@theqtcompany.com>
Reviewed-by: Eike Ziller <eike.ziller@theqtcompany.com>
Diffstat (limited to 'src/plugins/texteditor')
-rw-r--r-- | src/plugins/texteditor/indenter.cpp | 14 | ||||
-rw-r--r-- | src/plugins/texteditor/indenter.h | 2 | ||||
-rw-r--r-- | src/plugins/texteditor/normalindenter.cpp | 38 | ||||
-rw-r--r-- | src/plugins/texteditor/normalindenter.h | 9 | ||||
-rw-r--r-- | src/plugins/texteditor/tabsettings.cpp | 69 | ||||
-rw-r--r-- | src/plugins/texteditor/tabsettings.h | 18 | ||||
-rw-r--r-- | src/plugins/texteditor/textdocument.cpp | 11 | ||||
-rw-r--r-- | src/plugins/texteditor/texteditor.cpp | 6 | ||||
-rw-r--r-- | src/plugins/texteditor/texteditor_test.cpp | 130 | ||||
-rw-r--r-- | src/plugins/texteditor/texteditorplugin.h | 3 |
10 files changed, 217 insertions, 83 deletions
diff --git a/src/plugins/texteditor/indenter.cpp b/src/plugins/texteditor/indenter.cpp index 57573270aba..29e9f8e2a2c 100644 --- a/src/plugins/texteditor/indenter.cpp +++ b/src/plugins/texteditor/indenter.cpp @@ -25,6 +25,7 @@ #include "indenter.h" #include "tabsettings.h" +#include "textdocumentlayout.h" #include <QTextDocument> #include <QTextCursor> @@ -48,9 +49,11 @@ void Indenter::indentBlock(QTextDocument *doc, const TabSettings &tabSettings) { Q_UNUSED(doc); - Q_UNUSED(block); Q_UNUSED(typedChar); - Q_UNUSED(tabSettings); + const int indent = indentFor(block, tabSettings); + if (indent < 0) + return; + tabSettings.indentLine(block, indent); } void Indenter::indent(QTextDocument *doc, @@ -108,3 +111,10 @@ void Indenter::setCodeStylePreferences(ICodeStylePreferences *) void Indenter::invalidateCache(QTextDocument *) { } + +int Indenter::indentFor(const QTextBlock &block, const TabSettings &tabSettings) +{ + Q_UNUSED(block) + Q_UNUSED(tabSettings) + return -1; +} diff --git a/src/plugins/texteditor/indenter.h b/src/plugins/texteditor/indenter.h index d41bb020964..8f5093c9dd2 100644 --- a/src/plugins/texteditor/indenter.h +++ b/src/plugins/texteditor/indenter.h @@ -68,6 +68,8 @@ public: virtual void setCodeStylePreferences(ICodeStylePreferences *preferences); virtual void invalidateCache(QTextDocument *doc); + + virtual int indentFor(const QTextBlock &block, const TextEditor::TabSettings &tabSettings); }; } // namespace TextEditor diff --git a/src/plugins/texteditor/normalindenter.cpp b/src/plugins/texteditor/normalindenter.cpp index cc824be0303..d1956d83d89 100644 --- a/src/plugins/texteditor/normalindenter.cpp +++ b/src/plugins/texteditor/normalindenter.cpp @@ -28,14 +28,6 @@ #include <QTextDocument> -using namespace TextEditor; - -NormalIndenter::NormalIndenter() -{} - -NormalIndenter::~NormalIndenter() -{} - // Indent a text block based on previous line. // Simple text paragraph layout: // aaaa aaaa @@ -55,31 +47,21 @@ NormalIndenter::~NormalIndenter() // for additional block being inserted. It might be possible // to do in 2 steps (indenting/wrapping)} // -void NormalIndenter::indentBlock(QTextDocument *doc, - const QTextBlock &block, - const QChar &typedChar, - const TabSettings &tabSettings) + +using namespace TextEditor; + +int NormalIndenter::indentFor(const QTextBlock &block, const TabSettings &tabSettings) { - Q_UNUSED(typedChar) + Q_UNUSED(tabSettings); - // At beginning: Leave as is. - if (block == doc->begin()) - return; + QTextBlock previous = block.previous(); + if (!previous.isValid()) + return 0; - const QTextBlock previous = block.previous(); const QString previousText = previous.text(); // Empty line indicates a start of a new paragraph. Leave as is. if (previousText.isEmpty() || previousText.trimmed().isEmpty()) - return; + return 0; - // Just use previous line. - // Skip blank characters when determining the indentation - int i = 0; - while (i < previousText.size()) { - if (!previousText.at(i).isSpace()) { - tabSettings.indentLine(block, tabSettings.columnAt(previousText, i)); - break; - } - ++i; - } + return tabSettings.indentationColumn(previousText); } diff --git a/src/plugins/texteditor/normalindenter.h b/src/plugins/texteditor/normalindenter.h index af5d0c19c59..3cbbb6fd98b 100644 --- a/src/plugins/texteditor/normalindenter.h +++ b/src/plugins/texteditor/normalindenter.h @@ -33,13 +33,10 @@ namespace TextEditor { class TEXTEDITOR_EXPORT NormalIndenter : public Indenter { public: - NormalIndenter(); - virtual ~NormalIndenter(); + NormalIndenter() {} + ~NormalIndenter() override {} - virtual void indentBlock(QTextDocument *doc, - const QTextBlock &block, - const QChar &typedChar, - const TextEditor::TabSettings &tabSettings); + int indentFor(const QTextBlock &block, const TabSettings &tabSettings) override; }; } // namespace TextEditor diff --git a/src/plugins/texteditor/tabsettings.cpp b/src/plugins/texteditor/tabsettings.cpp index c396cf66799..0bf4bc29952 100644 --- a/src/plugins/texteditor/tabsettings.cpp +++ b/src/plugins/texteditor/tabsettings.cpp @@ -24,6 +24,7 @@ ****************************************************************************/ #include "tabsettings.h" +#include "texteditorplugin.h" #include <utils/settingsutils.h> @@ -42,12 +43,16 @@ static const char paddingModeKey[] = "PaddingMode"; namespace TextEditor { -TabSettings::TabSettings() : - m_tabPolicy(SpacesOnlyTabPolicy), - m_tabSize(8), - m_indentSize(4), - m_continuationAlignBehavior(ContinuationAlignWithSpaces) +TabSettings::TabSettings(TabSettings::TabPolicy tabPolicy, + int tabSize, + int indentSize, + TabSettings::ContinuationAlignBehavior continuationAlignBehavior) + : m_tabPolicy(tabPolicy) + , m_tabSize(tabSize) + , m_indentSize(indentSize) + , m_continuationAlignBehavior(continuationAlignBehavior) { + } void TabSettings::toSettings(const QString &category, QSettings *s) const @@ -157,7 +162,7 @@ void TabSettings::removeTrailingWhitespace(QTextCursor cursor, QTextBlock &block } } -bool TabSettings::isIndentationClean(const QTextBlock &block) const +bool TabSettings::isIndentationClean(const QTextBlock &block, const int indent) const { int i = 0; int spaceCount = 0; @@ -170,10 +175,16 @@ bool TabSettings::isIndentationClean(const QTextBlock &block) const if (c == QLatin1Char(' ')) { ++spaceCount; - if (!spacesForTabs && spaceCount == m_tabSize) + if (spaceCount == m_tabSize) + if (!spacesForTabs) + 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 (spacesForTabs || (spaceCount != 0)) + return false; + if ((m_continuationAlignBehavior != ContinuationAlignWithIndent) && ((i + 1) * m_tabSize > indent)) return false; } ++i; @@ -275,23 +286,29 @@ bool TabSettings::guessSpacesForTabs(const QTextBlock &_block) const return m_tabPolicy != TabsOnlyTabPolicy; } -QString TabSettings::indentationString(int startColumn, int targetColumn, const QTextBlock &block) const +QString TabSettings::indentationString(int startColumn, int targetColumn, int padding, + const QTextBlock &block) const { targetColumn = qMax(startColumn, targetColumn); if (guessSpacesForTabs(block)) return QString(targetColumn - startColumn, QLatin1Char(' ')); QString s; - int alignedStart = startColumn - (startColumn % m_tabSize) + m_tabSize; + int alignedStart = startColumn == 0 ? 0 : startColumn - (startColumn % m_tabSize) + m_tabSize; if (alignedStart > startColumn && alignedStart <= targetColumn) { s += QLatin1Char('\t'); startColumn = alignedStart; } - if (int columns = targetColumn - startColumn) { - int tabs = columns / m_tabSize; - s += QString(tabs, QLatin1Char('\t')); - s += QString(columns - tabs * m_tabSize, QLatin1Char(' ')); + if (m_continuationAlignBehavior == NoContinuationAlign) { + targetColumn -= padding; + padding = 0; + } else if (m_continuationAlignBehavior == ContinuationAlignWithIndent) { + padding = 0; } + const int columns = targetColumn - padding - startColumn; + const int tabs = columns / m_tabSize; + s += QString(tabs, QLatin1Char('\t')); + s += QString(targetColumn - startColumn - tabs * m_tabSize, QLatin1Char(' ')); return s; } @@ -313,15 +330,7 @@ void TabSettings::indentLine(QTextBlock block, int newIndent, int padding) const // if (indentationColumn(text) == newIndent) // return; - QString indentString; - - if (m_tabPolicy == TabsOnlyTabPolicy) { - // user likes tabs for spaces and uses tabs for indentation, preserve padding - indentString = indentationString(0, newIndent - padding, block); - indentString += QString(padding, QLatin1Char(' ')); - } else { - indentString = indentationString(0, newIndent, block); - } + const QString indentString = indentationString(0, newIndent, padding, block); if (oldBlockLength == indentString.length() && text == indentString) return; @@ -346,15 +355,11 @@ void TabSettings::reindentLine(QTextBlock block, int delta) const if (oldIndent == newIndent) return; - QString indentString; - if (m_tabPolicy == TabsOnlyTabPolicy && m_tabSize == m_indentSize) { - // user likes tabs for spaces and uses tabs for indentation, preserve padding - int padding = qMin(maximumPadding(text), newIndent); - indentString = indentationString(0, newIndent - padding, block); - indentString += QString(padding, QLatin1Char(' ')); - } else { - indentString = indentationString(0, newIndent, block); - } + int padding = 0; + // 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); if (oldBlockLength == indentString.length() && text == indentString) return; diff --git a/src/plugins/texteditor/tabsettings.h b/src/plugins/texteditor/tabsettings.h index a13a2ff87ba..f0b60584b73 100644 --- a/src/plugins/texteditor/tabsettings.h +++ b/src/plugins/texteditor/tabsettings.h @@ -55,7 +55,9 @@ public: ContinuationAlignWithIndent = 2 }; - TabSettings(); + TabSettings() = default; + TabSettings(TabPolicy tabPolicy, int tabSize, + int indentSize, ContinuationAlignBehavior continuationAlignBehavior); void toSettings(const QString &category, QSettings *s) const; void fromSettings(const QString &category, const QSettings *s); @@ -68,7 +70,7 @@ public: int positionAtColumn(const QString &text, int column, int *offset = 0, 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, const QTextBlock ¤tBlock = QTextBlock()) const; + QString indentationString(int startColumn, int targetColumn, int padding, const QTextBlock ¤tBlock = QTextBlock()) const; QString indentationString(const QString &text) const; int indentationColumn(const QString &text) const; static int maximumPadding(const QString &text); @@ -76,7 +78,7 @@ public: void indentLine(QTextBlock block, int newIndent, int padding = 0) const; void reindentLine(QTextBlock block, int delta) const; - bool isIndentationClean(const QTextBlock &block) const; + bool isIndentationClean(const QTextBlock &block, const int indent) const; bool guessSpacesForTabs(const QTextBlock &block) const; static int firstNonSpace(const QString &text); @@ -86,10 +88,10 @@ public: static int trailingWhitespaces(const QString &text); static void removeTrailingWhitespace(QTextCursor cursor, QTextBlock &block); - TabPolicy m_tabPolicy; - int m_tabSize; - int m_indentSize; - ContinuationAlignBehavior m_continuationAlignBehavior; + TabPolicy m_tabPolicy = SpacesOnlyTabPolicy; + int m_tabSize = 8; + int m_indentSize = 4; + ContinuationAlignBehavior m_continuationAlignBehavior = ContinuationAlignWithSpaces; bool equals(const TabSettings &ts) const; }; @@ -100,5 +102,7 @@ inline bool operator!=(const TabSettings &t1, const TabSettings &t2) { return !t } // namespace TextEditor Q_DECLARE_METATYPE(TextEditor::TabSettings) +Q_DECLARE_METATYPE(TextEditor::TabSettings::TabPolicy) +Q_DECLARE_METATYPE(TextEditor::TabSettings::ContinuationAlignBehavior) #endif // TABSETTINGS_H diff --git a/src/plugins/texteditor/textdocument.cpp b/src/plugins/texteditor/textdocument.cpp index 97a0209e1b7..6df2b7e35c0 100644 --- a/src/plugins/texteditor/textdocument.cpp +++ b/src/plugins/texteditor/textdocument.cpp @@ -142,7 +142,7 @@ QTextCursor TextDocumentPrivate::indentOrUnindent(const QTextCursor &textCursor, indentPosition = ts.firstNonSpace(text); int targetColumn = ts.indentedColumn(ts.columnAt(text, indentPosition), doIndent); cursor.setPosition(block.position() + indentPosition); - cursor.insertText(ts.indentationString(0, targetColumn, block)); + cursor.insertText(ts.indentationString(0, targetColumn, 0, block)); cursor.setPosition(block.position()); cursor.setPosition(block.position() + indentPosition, QTextCursor::KeepAnchor); cursor.removeSelectedText(); @@ -159,7 +159,7 @@ QTextCursor TextDocumentPrivate::indentOrUnindent(const QTextCursor &textCursor, int blockColumn = ts.columnAt(text, text.size()); if (blockColumn < column) { cursor.setPosition(block.position() + text.size()); - cursor.insertText(ts.indentationString(blockColumn, column, block)); + cursor.insertText(ts.indentationString(blockColumn, column, 0, block)); text = block.text(); } @@ -170,7 +170,7 @@ QTextCursor TextDocumentPrivate::indentOrUnindent(const QTextCursor &textCursor, cursor.setPosition(block.position() + indentPosition); cursor.setPosition(block.position() + indentPosition - spaces, QTextCursor::KeepAnchor); cursor.removeSelectedText(); - cursor.insertText(ts.indentationString(startColumn, targetColumn, block)); + cursor.insertText(ts.indentationString(startColumn, targetColumn, 0, block)); } // Preserve initial anchor of block selection if (blockSelection) { @@ -750,7 +750,8 @@ void TextDocument::cleanWhitespace(QTextCursor &cursor, bool cleanIndentation, b QString blockText = block.text(); d->m_tabSettings.removeTrailingWhitespace(cursor, block); - if (cleanIndentation && !d->m_tabSettings.isIndentationClean(block)) { + const int indent = d->m_indenter->indentFor(block, d->m_tabSettings); + if (cleanIndentation && !d->m_tabSettings.isIndentationClean(block, indent)) { cursor.setPosition(block.position()); int firstNonSpace = d->m_tabSettings.firstNonSpace(blockText); if (firstNonSpace == blockText.length()) { @@ -759,7 +760,7 @@ void TextDocument::cleanWhitespace(QTextCursor &cursor, bool cleanIndentation, b } else { int column = d->m_tabSettings.columnAt(blockText, firstNonSpace); cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, firstNonSpace); - QString indentationString = d->m_tabSettings.indentationString(0, column, block); + QString indentationString = d->m_tabSettings.indentationString(0, column, column - indent, block); cursor.insertText(indentationString); } } diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index f0653d4b767..a065d191ad9 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -3337,11 +3337,11 @@ void TextEditorWidgetPrivate::setCursorToColumn(QTextCursor &cursor, int column, cursor.setPosition(cursor.block().position() + pos - 1, QTextCursor::KeepAnchor); cursor.insertText(ts.indentationString( ts.columnAt(cursor.block().text(), pos - 1), - ts.columnAt(cursor.block().text(), pos), cursor.block())); + ts.columnAt(cursor.block().text(), pos), 0, cursor.block())); } else { // column is behind the last position cursor.insertText(ts.indentationString(ts.columnAt(cursor.block().text(), pos), - column, cursor.block())); + column, 0, cursor.block())); } if (moveMode == QTextCursor::KeepAnchor) cursor.setPosition(cursorPosition); @@ -6360,7 +6360,7 @@ void TextEditorWidget::rewrapParagraph() if (commonPrefix.isEmpty()) { spacing = d->m_document->tabSettings().indentationString( - 0, indentLevel, textCursor().block()); + 0, indentLevel, 0, textCursor().block()); } else { spacing = commonPrefix; indentLevel = commonPrefix.length(); diff --git a/src/plugins/texteditor/texteditor_test.cpp b/src/plugins/texteditor/texteditor_test.cpp index 3d645116ce3..786235d3608 100644 --- a/src/plugins/texteditor/texteditor_test.cpp +++ b/src/plugins/texteditor/texteditor_test.cpp @@ -36,6 +36,7 @@ #include "texteditor.h" #include "texteditorplugin.h" #include "textdocument.h" +#include "tabsettings.h" using namespace TextEditor; @@ -497,4 +498,133 @@ void Internal::TextEditorPlugin::testBlockSelectionCopy() Core::EditorManager::closeDocument(editor->document(), false); } +QString tabPolicyToString(TabSettings::TabPolicy policy) +{ + switch (policy) { + case TabSettings::SpacesOnlyTabPolicy: + return QLatin1String("spacesOnlyPolicy"); + case TabSettings::TabsOnlyTabPolicy: + return QLatin1String("tabsOnlyPolicy"); + case TabSettings::MixedTabPolicy: + return QLatin1String("mixedIndentPolicy"); + } + return QString(); +} + +QString continuationAlignBehaviorToString(TabSettings::ContinuationAlignBehavior behavior) +{ + switch (behavior) { + case TabSettings::NoContinuationAlign: + return QLatin1String("noContinuation"); + case TabSettings::ContinuationAlignWithSpaces: + return QLatin1String("spacesContinuation"); + case TabSettings::ContinuationAlignWithIndent: + return QLatin1String("indentContinuation"); + } + return QString(); +} + +struct TabSettingsFlags{ + TabSettings::TabPolicy policy; + TabSettings::ContinuationAlignBehavior behavior; +}; + +typedef std::function<bool(TabSettingsFlags)> IsClean; +void generateTestRows(QLatin1String name, QString text, IsClean isClean) +{ + QList<TabSettings::TabPolicy> allPolicys; + allPolicys << TabSettings::SpacesOnlyTabPolicy + << TabSettings::TabsOnlyTabPolicy + << TabSettings::MixedTabPolicy; + QList<TabSettings::ContinuationAlignBehavior> allbehavior; + allbehavior << TabSettings::NoContinuationAlign + << TabSettings::ContinuationAlignWithSpaces + << TabSettings::ContinuationAlignWithIndent; + + const QLatin1Char splitter('_'); + const int indentSize = 3; + + foreach (TabSettings::TabPolicy policy, allPolicys) { + foreach (TabSettings::ContinuationAlignBehavior behavior, allbehavior) { + const QString tag = tabPolicyToString(policy) + splitter + + continuationAlignBehaviorToString(behavior) + splitter + + name; + QTest::newRow(tag.toLatin1().data()) + << policy + << behavior + << text + << indentSize + << isClean({policy, behavior}); + } + } +} + +void Internal::TextEditorPlugin::testIndentationClean_data() +{ + QTest::addColumn<TabSettings::TabPolicy>("policy"); + QTest::addColumn<TabSettings::ContinuationAlignBehavior>("behavior"); + QTest::addColumn<QString>("text"); + QTest::addColumn<int>("indentSize"); + QTest::addColumn<bool>("clean"); + + generateTestRows(QLatin1String("emptyString"), QString::fromLatin1(""), + [](TabSettingsFlags) -> bool { + return true; + }); + + generateTestRows(QLatin1String("spaceIndentation"), QString::fromLatin1(" f"), + [](TabSettingsFlags flags) -> bool { + return flags.policy != TabSettings::TabsOnlyTabPolicy; + }); + + generateTestRows(QLatin1String("spaceIndentationGuessTabs"), QString::fromLatin1(" f\n\tf"), + [](TabSettingsFlags flags) -> bool { + return flags.policy == TabSettings::SpacesOnlyTabPolicy; + }); + + generateTestRows(QLatin1String("tabIndentation"), QString::fromLatin1("\tf"), + [](TabSettingsFlags flags) -> bool { + return flags.policy == TabSettings::TabsOnlyTabPolicy; + }); + + generateTestRows(QLatin1String("tabIndentationGuessTabs"), QString::fromLatin1("\tf\n\tf"), + [](TabSettingsFlags flags) -> bool { + return flags.policy != TabSettings::SpacesOnlyTabPolicy; + }); + + generateTestRows(QLatin1String("doubleSpaceIndentation"), QString::fromLatin1(" f"), + [](TabSettingsFlags flags) -> bool { + return flags.policy != TabSettings::TabsOnlyTabPolicy + && flags.behavior != TabSettings::NoContinuationAlign; + }); + + generateTestRows(QLatin1String("doubleTabIndentation"), QString::fromLatin1("\t\tf"), + [](TabSettingsFlags flags) -> bool { + return flags.policy == TabSettings::TabsOnlyTabPolicy + && flags.behavior == TabSettings::ContinuationAlignWithIndent; + }); + + generateTestRows(QLatin1String("tabSpaceIndentation"), QString::fromLatin1("\t f"), + [](TabSettingsFlags flags) -> bool { + return flags.policy == TabSettings::TabsOnlyTabPolicy + && flags.behavior == TabSettings::ContinuationAlignWithSpaces; + }); +} + +void Internal::TextEditorPlugin::testIndentationClean() +{ + // fetch test data + QFETCH(TabSettings::TabPolicy, policy); + QFETCH(TabSettings::ContinuationAlignBehavior, behavior); + QFETCH(QString, text); + QFETCH(int, indentSize); + QFETCH(bool, clean); + + const TabSettings settings(policy, indentSize, indentSize, behavior); + const QTextDocument doc(text); + const QTextBlock block = doc.firstBlock(); + + QCOMPARE(settings.isIndentationClean(block, indentSize), clean); +} + #endif // ifdef WITH_TESTS diff --git a/src/plugins/texteditor/texteditorplugin.h b/src/plugins/texteditor/texteditorplugin.h index b66feb99db7..f8486caa67b 100644 --- a/src/plugins/texteditor/texteditorplugin.h +++ b/src/plugins/texteditor/texteditorplugin.h @@ -79,6 +79,9 @@ private slots: void testBlockSelectionRemove(); void testBlockSelectionCopy_data(); void testBlockSelectionCopy(); + + void testIndentationClean_data(); + void testIndentationClean(); #endif }; |