diff --git a/src/plugins/texteditor/behaviorsettingswidget.cpp b/src/plugins/texteditor/behaviorsettingswidget.cpp index f5dc751eb6a..fe7e983fade 100644 --- a/src/plugins/texteditor/behaviorsettingswidget.cpp +++ b/src/plugins/texteditor/behaviorsettingswidget.cpp @@ -101,6 +101,8 @@ BehaviorSettingsWidget::BehaviorSettingsWidget(QWidget *parent) this, &BehaviorSettingsWidget::slotStorageSettingsChanged); connect(d->m_ui.cleanIndentation, &QAbstractButton::clicked, this, &BehaviorSettingsWidget::slotStorageSettingsChanged); + connect(d->m_ui.skipTrailingWhitespace, &QAbstractButton::clicked, + this, &BehaviorSettingsWidget::slotStorageSettingsChanged); connect(d->m_ui.mouseHiding, &QAbstractButton::clicked, this, &BehaviorSettingsWidget::slotBehaviorSettingsChanged); connect(d->m_ui.mouseNavigation, &QAbstractButton::clicked, @@ -190,6 +192,9 @@ void BehaviorSettingsWidget::setAssignedStorageSettings(const StorageSettings &s d->m_ui.inEntireDocument->setChecked(storageSettings.m_inEntireDocument); d->m_ui.cleanIndentation->setChecked(storageSettings.m_cleanIndentation); d->m_ui.addFinalNewLine->setChecked(storageSettings.m_addFinalNewLine); + d->m_ui.skipTrailingWhitespace->setChecked(storageSettings.m_skipTrailingWhitespace); + d->m_ui.ignoreFileTypes->setText(storageSettings.m_ignoreFileTypes); + d->m_ui.ignoreFileTypes->setEnabled(d->m_ui.skipTrailingWhitespace->isChecked()); } void BehaviorSettingsWidget::assignedStorageSettings(StorageSettings *storageSettings) const @@ -198,6 +203,8 @@ void BehaviorSettingsWidget::assignedStorageSettings(StorageSettings *storageSet storageSettings->m_inEntireDocument = d->m_ui.inEntireDocument->isChecked(); storageSettings->m_cleanIndentation = d->m_ui.cleanIndentation->isChecked(); storageSettings->m_addFinalNewLine = d->m_ui.addFinalNewLine->isChecked(); + storageSettings->m_skipTrailingWhitespace = d->m_ui.skipTrailingWhitespace->isChecked(); + storageSettings->m_ignoreFileTypes = d->m_ui.ignoreFileTypes->text(); } void BehaviorSettingsWidget::updateConstrainTooltipsBoxTooltip() const @@ -273,6 +280,10 @@ void BehaviorSettingsWidget::slotStorageSettingsChanged() { StorageSettings settings; assignedStorageSettings(&settings); + + bool ignoreFileTypesEnabled = d->m_ui.cleanWhitespace->isChecked() && d->m_ui.skipTrailingWhitespace->isChecked(); + d->m_ui.ignoreFileTypes->setEnabled(ignoreFileTypesEnabled); + emit storageSettingsChanged(settings); } diff --git a/src/plugins/texteditor/behaviorsettingswidget.ui b/src/plugins/texteditor/behaviorsettingswidget.ui index c36bb9ae47b..d6d9fed5c60 100644 --- a/src/plugins/texteditor/behaviorsettingswidget.ui +++ b/src/plugins/texteditor/behaviorsettingswidget.ui @@ -7,7 +7,7 @@ 0 0 801 - 547 + 693 @@ -169,13 +169,72 @@ Specifies how backspace interacts with indentation. Cleanups Upon Saving - - + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + false + + + For the file patterns listed, do not trim trailing whitespace. + + + Skip clean whitespace for file types: + + + false + + + false + + + + + + + false + + + false + + + List of wildcard-aware file patterns, separated by commas or semicolons. + + + + + + + + + + + + + true + - Removes trailing whitespace upon saving. + Always writes a newline character at the end of the file. - &Clean whitespace + &Ensure newline at end of file @@ -195,6 +254,29 @@ Specifies how backspace interacts with indentation. + + + + Removes trailing whitespace upon saving. + + + &Clean whitespace + + + + + + + false + + + Corrects leading whitespace according to tab settings. + + + Clean indentation + + + @@ -214,29 +296,6 @@ Specifies how backspace interacts with indentation. - - - - false - - - Corrects leading whitespace according to tab settings. - - - Clean indentation - - - - - - - Always writes a newline character at the end of the file. - - - &Ensure newline at end of file - - - @@ -521,5 +580,37 @@ Specifies how backspace interacts with indentation. + + cleanWhitespace + toggled(bool) + skipTrailingWhitespace + setEnabled(bool) + + + 530 + 48 + + + 548 + 144 + + + + + cleanWhitespace + toggled(bool) + ignoreFileTypes + setEnabled(bool) + + + 530 + 48 + + + 556 + 177 + + + diff --git a/src/plugins/texteditor/storagesettings.cpp b/src/plugins/texteditor/storagesettings.cpp index a1c4645b0cf..2053ffa440c 100644 --- a/src/plugins/texteditor/storagesettings.cpp +++ b/src/plugins/texteditor/storagesettings.cpp @@ -27,6 +27,7 @@ #include +#include #include #include @@ -36,13 +37,18 @@ static const char cleanWhitespaceKey[] = "cleanWhitespace"; static const char inEntireDocumentKey[] = "inEntireDocument"; static const char addFinalNewLineKey[] = "addFinalNewLine"; static const char cleanIndentationKey[] = "cleanIndentation"; +static const char skipTrailingWhitespaceKey[] = "skipTrailingWhitespace"; +static const char ignoreFileTypesKey[] = "ignoreFileTypes"; static const char groupPostfix[] = "StorageSettings"; +static const char defaultTrailingWhitespaceBlacklist[] = "*.md, *.MD, Makefile"; StorageSettings::StorageSettings() - : m_cleanWhitespace(true), + : m_ignoreFileTypes(defaultTrailingWhitespaceBlacklist), + m_cleanWhitespace(true), m_inEntireDocument(false), m_addFinalNewLine(true), - m_cleanIndentation(true) + m_cleanIndentation(true), + m_skipTrailingWhitespace(true) { } @@ -63,6 +69,8 @@ void StorageSettings::toMap(const QString &prefix, QVariantMap *map) const map->insert(prefix + QLatin1String(inEntireDocumentKey), m_inEntireDocument); map->insert(prefix + QLatin1String(addFinalNewLineKey), m_addFinalNewLine); map->insert(prefix + QLatin1String(cleanIndentationKey), m_cleanIndentation); + map->insert(prefix + QLatin1String(skipTrailingWhitespaceKey), m_skipTrailingWhitespace); + map->insert(prefix + QLatin1String(ignoreFileTypesKey), m_ignoreFileTypes.toLatin1().data()); } void StorageSettings::fromMap(const QString &prefix, const QVariantMap &map) @@ -75,6 +83,42 @@ void StorageSettings::fromMap(const QString &prefix, const QVariantMap &map) map.value(prefix + QLatin1String(addFinalNewLineKey), m_addFinalNewLine).toBool(); m_cleanIndentation = map.value(prefix + QLatin1String(cleanIndentationKey), m_cleanIndentation).toBool(); + m_skipTrailingWhitespace = + map.value(prefix + QLatin1String(skipTrailingWhitespaceKey), m_skipTrailingWhitespace).toBool(); + m_ignoreFileTypes = + map.value(prefix + QLatin1String(ignoreFileTypesKey), m_ignoreFileTypes).toString(); +} + +bool StorageSettings::removeTrailingWhitespace(const QString &fileName) const +{ + // if the user has elected not to trim trailing whitespace altogether, then + // early out here + if (!m_skipTrailingWhitespace) { + return true; + } + + const QString ignoreFileTypesRegExp(R"(\s*((?>\*\.)?[\w\d\.\*]+)[,;]?\s*)"); + + // use the ignore-files regex to extract the specified file patterns + QRegularExpression re(ignoreFileTypesRegExp); + QRegularExpressionMatchIterator iter = re.globalMatch(m_ignoreFileTypes); + + while (iter.hasNext()) { + QRegularExpressionMatch match = iter.next(); + QString pattern = match.captured(1); + + QString wildcardRegExp = QRegularExpression::wildcardToRegularExpression(pattern); + QRegularExpression patternRegExp(wildcardRegExp); + QRegularExpressionMatch patternMatch = patternRegExp.match(fileName); + if (patternMatch.hasMatch()) { + // if the filename has a pattern we want to ignore, then we need to return + // false ("don't remove trailing whitespace") + return false; + } + } + + // the supplied pattern does not match, so we want to remove trailing whitespace + return true; } bool StorageSettings::equals(const StorageSettings &ts) const @@ -82,7 +126,9 @@ bool StorageSettings::equals(const StorageSettings &ts) const return m_addFinalNewLine == ts.m_addFinalNewLine && m_cleanWhitespace == ts.m_cleanWhitespace && m_inEntireDocument == ts.m_inEntireDocument - && m_cleanIndentation == ts.m_cleanIndentation; + && m_cleanIndentation == ts.m_cleanIndentation + && m_skipTrailingWhitespace == ts.m_skipTrailingWhitespace + && m_ignoreFileTypes == ts.m_ignoreFileTypes; } } // namespace TextEditor diff --git a/src/plugins/texteditor/storagesettings.h b/src/plugins/texteditor/storagesettings.h index ffe7823f0fd..cd9c176b597 100644 --- a/src/plugins/texteditor/storagesettings.h +++ b/src/plugins/texteditor/storagesettings.h @@ -46,12 +46,17 @@ public: void toMap(const QString &prefix, QVariantMap *map) const; void fromMap(const QString &prefix, const QVariantMap &map); + // calculated based on boolean setting plus file type blacklist examination + bool removeTrailingWhitespace(const QString &filePattern) const; + bool equals(const StorageSettings &ts) const; + QString m_ignoreFileTypes; bool m_cleanWhitespace; bool m_inEntireDocument; bool m_addFinalNewLine; bool m_cleanIndentation; + bool m_skipTrailingWhitespace; }; inline bool operator==(const StorageSettings &t1, const StorageSettings &t2) { return t1.equals(t2); } diff --git a/src/plugins/texteditor/textdocument.cpp b/src/plugins/texteditor/textdocument.cpp index 319f77a3978..0f3ef7a05f5 100644 --- a/src/plugins/texteditor/textdocument.cpp +++ b/src/plugins/texteditor/textdocument.cpp @@ -622,7 +622,7 @@ bool TextDocument::save(QString *errorString, const QString &saveFileName, bool cursor.movePosition(QTextCursor::Start); if (d->m_storageSettings.m_cleanWhitespace) - cleanWhitespace(cursor, d->m_storageSettings.m_cleanIndentation, d->m_storageSettings.m_inEntireDocument); + cleanWhitespace(cursor, d->m_storageSettings); if (d->m_storageSettings.m_addFinalNewLine) ensureFinalNewLine(cursor); cursor.endEditBlock(); @@ -883,14 +883,22 @@ void TextDocument::cleanWhitespace(const QTextCursor &cursor) QTextCursor copyCursor = cursor; copyCursor.setVisualNavigation(false); copyCursor.beginEditBlock(); - cleanWhitespace(copyCursor, true, true); + + cleanWhitespace(copyCursor, d->m_storageSettings); + if (!hasSelection) ensureFinalNewLine(copyCursor); + copyCursor.endEditBlock(); } -void TextDocument::cleanWhitespace(QTextCursor &cursor, bool cleanIndentation, bool inEntireDocument) +void TextDocument::cleanWhitespace(QTextCursor &cursor, const StorageSettings &storageSettings) { + if (!d->m_storageSettings.m_cleanWhitespace) + return; + + const QString fileName(filePath().fileName()); + auto documentLayout = qobject_cast(d->m_document.documentLayout()); Q_ASSERT(cursor.visualNavigation() == false); @@ -901,7 +909,7 @@ void TextDocument::cleanWhitespace(QTextCursor &cursor, bool cleanIndentation, b QVector blocks; while (block.isValid() && block != end) { - if (inEntireDocument || block.revision() != documentLayout->lastSaveRevision) + if (storageSettings.m_inEntireDocument || block.revision() != documentLayout->lastSaveRevision) blocks.append(block); block = block.next(); } @@ -914,9 +922,12 @@ void TextDocument::cleanWhitespace(QTextCursor &cursor, bool cleanIndentation, b foreach (block, blocks) { QString blockText = block.text(); - currentTabSettings.removeTrailingWhitespace(cursor, block); + + if (storageSettings.removeTrailingWhitespace(fileName)) + currentTabSettings.removeTrailingWhitespace(cursor, block); + const int indent = indentations[block.blockNumber()]; - if (cleanIndentation && !currentTabSettings.isIndentationClean(block, indent)) { + if (storageSettings.m_cleanIndentation && !currentTabSettings.isIndentationClean(block, indent)) { cursor.setPosition(block.position()); int firstNonSpace = currentTabSettings.firstNonSpace(blockText); if (firstNonSpace == blockText.length()) { @@ -934,6 +945,9 @@ void TextDocument::cleanWhitespace(QTextCursor &cursor, bool cleanIndentation, b void TextDocument::ensureFinalNewLine(QTextCursor& cursor) { + if (!d->m_storageSettings.m_addFinalNewLine) + return; + cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor); bool emptyFile = !cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); diff --git a/src/plugins/texteditor/textdocument.h b/src/plugins/texteditor/textdocument.h index 447edd0e508..35e6b334eff 100644 --- a/src/plugins/texteditor/textdocument.h +++ b/src/plugins/texteditor/textdocument.h @@ -169,7 +169,7 @@ protected: private: OpenResult openImpl(QString *errorString, const QString &fileName, const QString &realFileName, bool reload); - void cleanWhitespace(QTextCursor &cursor, bool cleanIndentation, bool inEntireDocument); + void cleanWhitespace(QTextCursor &cursor, const StorageSettings &storageSettings); void ensureFinalNewLine(QTextCursor &cursor); void modificationChanged(bool modified); void updateLayout() const;