Commit 058d2e8c authored by Francois Ferrand's avatar Francois Ferrand Committed by Eike Ziller

Support preserving case when replacing.

When making a case insensitive search, try to keep the string capitalization when doing
the replace:
      - All upper-case matches are replaced with the upper-case new	text.
      - All lower-case matches are replaced with the lower-case new text.
      - Capitalized matches are replace with the capitalized new text.
      - Other matches are replaced with the new text as provided.

Note: this does not work with regexp replace, only plain text.

Change-Id: I87cbc28eb64688bdf3c8c6ec173fcb22f91abcd0
Reviewed-by: default avatarCristian Tibirna <tibirna@kde.org>
Reviewed-by: default avatarLeena Miettinen <riitta-leena.miettinen@digia.com>
Reviewed-by: default avatarEike Ziller <eike.ziller@digia.com>
parent a8a33b9a
......@@ -1172,6 +1172,28 @@
\endlist
The \gui{Preserve Case when Replacing} option can be selected to preserve
the case of the original text when replacing. This option is not compatible
with the \gui {Regular Expressions} search option, and will thus be
disabled when regular expressions are used. When the option is used, the
case of the occurrence will be conserved, according to the following rules:
\list
\o All upper-case occurrences are replaced with the upper-case new text.
\o All lower-case occurrences are replaced with the lower-case new text.
\o Capitalized occurrences are replaced with the capitalized new text.
\o Other occurrences are replaced with the new text as entered.
\o If an occurrence and the new text have the same prefix or suffix,
then the case of the prefix and/or suffix are preserved, and the
other rules are applied on the rest of the occurrence only.
\endlist
\section1 Advanced Search
To search through projects, files on a file system or the currently open
......
......@@ -348,6 +348,72 @@ QString Utils::expandRegExpReplacement(const QString &replaceText, const QString
return result;
}
namespace Utils {
namespace Internal {
QString matchCaseReplacement(const QString &originalText, const QString &replaceText)
{
//Now proceed with actual case matching
bool firstIsUpperCase = originalText.at(0).isUpper();
bool firstIsLowerCase = originalText.at(0).isLower();
bool restIsLowerCase = true; // to be verified
bool restIsUpperCase = true; // to be verified
for (int i = 1; i < originalText.length(); ++i) {
if (originalText.at(i).isUpper())
restIsLowerCase = false;
else if (originalText.at(i).isLower())
restIsUpperCase = false;
if (!restIsLowerCase && !restIsUpperCase)
return replaceText; // mixed
}
if (restIsLowerCase) {
QString res = replaceText.toLower();
if (firstIsUpperCase)
res.replace(0, 1, res.at(0).toUpper());
return res;
}
if (restIsUpperCase) {
QString res = replaceText.toUpper();
if (firstIsLowerCase)
res.replace(0, 1, res.at(0).toLower());
return res;
}
return replaceText; // mixed
}
}
}
QString Utils::matchCaseReplacement(const QString &originalText, const QString &replaceText)
{
if (originalText.isEmpty())
return replaceText;
//Find common prefix & suffix: these will be unaffected
const int replaceTextLen = replaceText.length();
const int originalTextLen = originalText.length();
int prefixLen = 0;
for (; prefixLen <= replaceTextLen && prefixLen <= originalTextLen; prefixLen++)
if (replaceText.at(prefixLen).toLower() != originalText.at(prefixLen).toLower())
break;
int suffixLen = 0;
for (; suffixLen < replaceTextLen - prefixLen && suffixLen < originalTextLen - prefixLen; suffixLen++)
if (replaceText.at(replaceTextLen - 1 - suffixLen).toLower() != originalText.at(originalTextLen- 1 - suffixLen).toLower())
break;
//keep prefix and suffix, and do actual replacement on the 'middle' of the string
return originalText.left(prefixLen)
+ Internal::matchCaseReplacement(originalText.mid(prefixLen, originalTextLen - prefixLen - suffixLen),
replaceText.mid(prefixLen, replaceTextLen - prefixLen - suffixLen))
+ originalText.right(suffixLen);
}
// #pragma mark -- FileIterator
FileIterator::FileIterator()
......
......@@ -118,6 +118,7 @@ QTCREATOR_UTILS_EXPORT QFuture<FileSearchResultList> findInFilesRegExp(const QSt
QTextDocument::FindFlags flags, QMap<QString, QString> fileToContentsMap = QMap<QString, QString>());
QTCREATOR_UTILS_EXPORT QString expandRegExpReplacement(const QString &replaceText, const QStringList &capturedTexts);
QTCREATOR_UTILS_EXPORT QString matchCaseReplacement(const QString &originalText, const QString &replaceText);
} // namespace Utils
......
......@@ -255,8 +255,8 @@ void CppFindReferences::findUsages(CPlusPlus::Symbol *symbol,
: Find::SearchResultWindow::SearchOnly,
QLatin1String("CppEditor"));
search->setTextToReplace(replacement);
connect(search, SIGNAL(replaceButtonClicked(QString,QList<Find::SearchResultItem>)),
SLOT(onReplaceButtonClicked(QString,QList<Find::SearchResultItem>)));
connect(search, SIGNAL(replaceButtonClicked(QString,QList<Find::SearchResultItem>,bool)),
SLOT(onReplaceButtonClicked(QString,QList<Find::SearchResultItem>,bool)));
connect(search, SIGNAL(paused(bool)), this, SLOT(setPaused(bool)));
search->setSearchAgainSupported(true);
connect(search, SIGNAL(searchAgainRequested()), this, SLOT(searchAgain()));
......@@ -303,9 +303,10 @@ void CppFindReferences::findAll_helper(Find::SearchResult *search)
}
void CppFindReferences::onReplaceButtonClicked(const QString &text,
const QList<Find::SearchResultItem> &items)
const QList<Find::SearchResultItem> &items,
bool preserveCase)
{
const QStringList fileNames = TextEditor::BaseFileFind::replaceAll(text, items);
const QStringList fileNames = TextEditor::BaseFileFind::replaceAll(text, items, preserveCase);
if (!fileNames.isEmpty()) {
_modelManager->updateSourceFiles(fileNames);
Find::SearchResultWindow::instance()->hide();
......
......@@ -89,7 +89,7 @@ private Q_SLOTS:
void cancel();
void setPaused(bool paused);
void openEditor(const Find::SearchResultItem &item);
void onReplaceButtonClicked(const QString &text, const QList<Find::SearchResultItem> &items);
void onReplaceButtonClicked(const QString &text, const QList<Find::SearchResultItem> &items, bool preserveCase);
void searchAgain();
private:
......
......@@ -124,7 +124,8 @@ bool BaseTextFind::supportsReplace() const
Find::FindFlags BaseTextFind::supportedFindFlags() const
{
return Find::FindBackward | Find::FindCaseSensitively
| Find::FindRegularExpression | Find::FindWholeWords;
| Find::FindRegularExpression | Find::FindWholeWords
| Find::FindPreserveCase;
}
void BaseTextFind::resetIncrementalSearch()
......@@ -216,12 +217,19 @@ QTextCursor BaseTextFind::replaceInternal(const QString &before, const QString &
{
QTextCursor cursor = textCursor();
bool usesRegExp = (findFlags & Find::FindRegularExpression);
bool preserveCase = (findFlags & Find::FindPreserveCase);
QRegExp regexp(before,
(findFlags & Find::FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive,
usesRegExp ? QRegExp::RegExp : QRegExp::FixedString);
if (regexp.exactMatch(cursor.selectedText())) {
QString realAfter = usesRegExp ? Utils::expandRegExpReplacement(after, regexp.capturedTexts()) : after;
QString realAfter;
if (usesRegExp)
realAfter = Utils::expandRegExpReplacement(after, regexp.capturedTexts());
else if (preserveCase)
realAfter = Utils::matchCaseReplacement(cursor.selectedText(), after);
else
realAfter = after;
int start = cursor.selectionStart();
cursor.insertText(realAfter);
if ((findFlags&Find::FindBackward) != 0)
......@@ -252,6 +260,7 @@ int BaseTextFind::replaceAll(const QString &before, const QString &after,
editCursor.beginEditBlock();
int count = 0;
bool usesRegExp = (findFlags & Find::FindRegularExpression);
bool preserveCase = (findFlags & Find::FindPreserveCase);
QRegExp regexp(before);
regexp.setPatternSyntax(usesRegExp ? QRegExp::RegExp : QRegExp::FixedString);
regexp.setCaseSensitivity((findFlags & Find::FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive);
......@@ -263,7 +272,14 @@ int BaseTextFind::replaceAll(const QString &before, const QString &after,
editCursor.setPosition(found.selectionStart());
editCursor.setPosition(found.selectionEnd(), QTextCursor::KeepAnchor);
regexp.exactMatch(found.selectedText());
QString realAfter = usesRegExp ? Utils::expandRegExpReplacement(after, regexp.capturedTexts()) : after;
QString realAfter;
if (usesRegExp)
realAfter = Utils::expandRegExpReplacement(after, regexp.capturedTexts());
else if (preserveCase)
realAfter = Utils::matchCaseReplacement(found.selectedText(), after);
else
realAfter = after;
editCursor.insertText(realAfter);
found = findOne(regexp, editCursor, Find::textDocumentFlagsForFindFlags(findFlags));
}
......
......@@ -5,5 +5,6 @@
<file>images/regexp.png</file>
<file>images/expand.png</file>
<file>images/wrapindicator.png</file>
<file>images/preservecase.png</file>
</qresource>
</RCC>
......@@ -270,6 +270,11 @@ void FindPlugin::setRegularExpression(bool regExp)
setFindFlag(Find::FindRegularExpression, regExp);
}
void FindPlugin::setPreserveCase(bool preserveCase)
{
setFindFlag(Find::FindPreserveCase, preserveCase);
}
void FindPlugin::setFindFlag(Find::FindFlag flag, bool enabled)
{
bool hasFlag = hasFindFlag(flag);
......@@ -296,6 +301,7 @@ void FindPlugin::writeSettings()
settings->setValue(QLatin1String("CaseSensitively"), hasFindFlag(Find::FindCaseSensitively));
settings->setValue(QLatin1String("WholeWords"), hasFindFlag(Find::FindWholeWords));
settings->setValue(QLatin1String("RegularExpression"), hasFindFlag(Find::FindRegularExpression));
settings->setValue(QLatin1String("PreserveCase"), hasFindFlag(Find::FindPreserveCase));
settings->setValue(QLatin1String("FindStrings"), d->m_findCompletions);
settings->setValue(QLatin1String("ReplaceStrings"), d->m_replaceCompletions);
settings->endGroup();
......@@ -312,6 +318,7 @@ void FindPlugin::readSettings()
setCaseSensitive(settings->value(QLatin1String("CaseSensitively"), false).toBool());
setWholeWord(settings->value(QLatin1String("WholeWords"), false).toBool());
setRegularExpression(settings->value(QLatin1String("RegularExpression"), false).toBool());
setPreserveCase(settings->value(QLatin1String("PreserveCase"), false).toBool());
blockSignals(block);
d->m_findCompletions = settings->value(QLatin1String("FindStrings")).toStringList();
d->m_replaceCompletions = settings->value(QLatin1String("ReplaceStrings")).toStringList();
......
......@@ -83,6 +83,7 @@ public slots:
void setWholeWord(bool wholeOnly);
void setBackward(bool backward);
void setRegularExpression(bool regExp);
void setPreserveCase(bool preserveCase);
signals:
void findFlagsChanged();
......
......@@ -250,6 +250,15 @@ FindToolBar::FindToolBar(FindPlugin *plugin, CurrentDocumentFind *currentDocumen
connect(m_regularExpressionAction, SIGNAL(triggered(bool)), this, SLOT(setRegularExpressions(bool)));
lineEditMenu->addAction(m_regularExpressionAction);
m_preserveCaseAction = new QAction(tr("Preserve Case when Replacing"), this);
m_preserveCaseAction->setIcon(QPixmap(QLatin1String(":/find/images/preservecase.png")));
m_preserveCaseAction->setCheckable(true);
m_preserveCaseAction->setChecked(false);
cmd = Core::ActionManager::registerAction(m_preserveCaseAction, Constants::PRESERVE_CASE, globalcontext);
mfind->addAction(cmd, Constants::G_FIND_FLAGS);
connect(m_preserveCaseAction, SIGNAL(triggered(bool)), this, SLOT(setPreserveCase(bool)));
lineEditMenu->addAction(m_preserveCaseAction);
connect(m_currentDocumentFind, SIGNAL(candidateChanged()), this, SLOT(adaptToCandidate()));
connect(m_currentDocumentFind, SIGNAL(changed()), this, SLOT(updateToolBar()));
updateToolBar();
......@@ -357,6 +366,7 @@ void FindToolBar::updateToolBar()
m_caseSensitiveAction->setEnabled(enabled);
m_wholeWordAction->setEnabled(enabled);
m_regularExpressionAction->setEnabled(enabled);
m_preserveCaseAction->setEnabled(replaceEnabled && !hasFindFlag(Find::FindRegularExpression));
if (QApplication::clipboard()->supportsFindBuffer())
m_enterFindStringAction->setEnabled(enabled);
bool replaceFocus = m_ui.replaceEdit->hasFocus();
......@@ -549,7 +559,8 @@ void FindToolBar::updateIcons()
bool casesensitive = effectiveFlags & Find::FindCaseSensitively;
bool wholewords = effectiveFlags & Find::FindWholeWords;
bool regexp = effectiveFlags & Find::FindRegularExpression;
if (!casesensitive && !wholewords && !regexp) {
bool preserveCase = effectiveFlags & Find::FindPreserveCase;
if (!casesensitive && !wholewords && !regexp && !preserveCase) {
QPixmap pixmap(17, 17);
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
......@@ -565,10 +576,15 @@ void FindToolBar::updateIcons()
Find::FindFlags FindToolBar::effectiveFindFlags()
{
Find::FindFlags supportedFlags;
if (m_currentDocumentFind->isEnabled())
bool supportsReplace = true;
if (m_currentDocumentFind->isEnabled()) {
supportedFlags = m_currentDocumentFind->supportedFindFlags();
else
supportsReplace = m_currentDocumentFind->supportsReplace();
} else {
supportedFlags = (Find::FindFlags)0xFFFFFF;
}
if (!supportsReplace || m_findFlags & Find::FindRegularExpression)
supportedFlags &= ~Find::FindPreserveCase;
return supportedFlags & m_findFlags;
}
......@@ -577,18 +593,23 @@ void FindToolBar::updateFlagMenus()
bool wholeOnly = ((m_findFlags & Find::FindWholeWords));
bool sensitive = ((m_findFlags & Find::FindCaseSensitively));
bool regexp = ((m_findFlags & Find::FindRegularExpression));
bool preserveCase = ((m_findFlags & Find::FindPreserveCase));
if (m_wholeWordAction->isChecked() != wholeOnly)
m_wholeWordAction->setChecked(wholeOnly);
if (m_caseSensitiveAction->isChecked() != sensitive)
m_caseSensitiveAction->setChecked(sensitive);
if (m_regularExpressionAction->isChecked() != regexp)
m_regularExpressionAction->setChecked(regexp);
if (m_preserveCaseAction->isChecked() != preserveCase)
m_preserveCaseAction->setChecked(preserveCase);
Find::FindFlags supportedFlags;
if (m_currentDocumentFind->isEnabled())
supportedFlags = m_currentDocumentFind->supportedFindFlags();
m_wholeWordAction->setEnabled(supportedFlags & Find::FindWholeWords);
m_caseSensitiveAction->setEnabled(supportedFlags & Find::FindCaseSensitively);
m_regularExpressionAction->setEnabled(supportedFlags & Find::FindRegularExpression);
bool replaceEnabled = m_currentDocumentFind->isEnabled() && m_currentDocumentFind->supportsReplace();
m_preserveCaseAction->setEnabled((supportedFlags & Find::FindPreserveCase) && !regexp && replaceEnabled);
}
bool FindToolBar::setFocusToCurrentFindSupport()
......@@ -682,6 +703,7 @@ void FindToolBar::writeSettings()
settings->setValue(QLatin1String("CaseSensitively"), QVariant((m_findFlags & Find::FindCaseSensitively) != 0));
settings->setValue(QLatin1String("WholeWords"), QVariant((m_findFlags & Find::FindWholeWords) != 0));
settings->setValue(QLatin1String("RegularExpression"), QVariant((m_findFlags & Find::FindRegularExpression) != 0));
settings->setValue(QLatin1String("PreserveCase"), QVariant((m_findFlags & Find::FindPreserveCase) != 0));
settings->endGroup();
settings->endGroup();
}
......@@ -700,6 +722,8 @@ void FindToolBar::readSettings()
flags |= Find::FindWholeWords;
if (settings->value(QLatin1String("RegularExpression"), false).toBool())
flags |= Find::FindRegularExpression;
if (settings->value(QLatin1String("PreserveCase"), false).toBool())
flags |= Find::FindPreserveCase;
settings->endGroup();
settings->endGroup();
m_findFlags = flags;
......@@ -744,6 +768,11 @@ void FindToolBar::setRegularExpressions(bool regexp)
setFindFlag(Find::FindRegularExpression, regexp);
}
void FindToolBar::setPreserveCase(bool preserveCase)
{
setFindFlag(Find::FindPreserveCase, preserveCase);
}
void FindToolBar::setBackward(bool backward)
{
setFindFlag(Find::FindBackward, backward);
......
......@@ -91,6 +91,7 @@ private slots:
void setCaseSensitive(bool sensitive);
void setWholeWord(bool wholeOnly);
void setRegularExpressions(bool regexp);
void setPreserveCase(bool preserveCase);
void adaptToCandidate();
......@@ -132,6 +133,7 @@ private:
QAction *m_caseSensitiveAction;
QAction *m_wholeWordAction;
QAction *m_regularExpressionAction;
QAction *m_preserveCaseAction;
Find::FindFlags m_findFlags;
QTimer m_findIncrementalTimer;
......
......@@ -225,13 +225,16 @@ QPixmap Find::IFindFilter::pixmapForFindFlags(Find::FindFlags flags)
static const QPixmap casesensitiveIcon = QPixmap(QLatin1String(":/find/images/casesensitively.png"));
static const QPixmap regexpIcon = QPixmap(QLatin1String(":/find/images/regexp.png"));
static const QPixmap wholewordsIcon = QPixmap(QLatin1String(":/find/images/wholewords.png"));
static const QPixmap preservecaseIcon = QPixmap(QLatin1String(":/find/images/preservecase.png"));
bool casesensitive = flags & Find::FindCaseSensitively;
bool wholewords = flags & Find::FindWholeWords;
bool regexp = flags & Find::FindRegularExpression;
bool preservecase = flags & Find::FindPreserveCase;
int width = 0;
if (casesensitive) width += 6;
if (wholewords) width += 6;
if (regexp) width += 6;
if (preservecase) width += 6;
if (width > 0) --width;
QPixmap pixmap(width, 17);
pixmap.fill(Qt::transparent);
......@@ -248,6 +251,10 @@ QPixmap Find::IFindFilter::pixmapForFindFlags(Find::FindFlags flags)
}
if (regexp) {
painter.drawPixmap(x - 6, 0, regexpIcon);
x += 6;
}
if (preservecase) {
painter.drawPixmap(x - 6, 0, preservecaseIcon);
}
return pixmap;
}
......@@ -261,6 +268,8 @@ QString Find::IFindFilter::descriptionForFindFlags(Find::FindFlags flags)
flagStrings.append(tr("Whole words"));
if (flags & Find::FindRegularExpression)
flagStrings.append(tr("Regular expressions"));
if (flags & Find::FindPreserveCase)
flagStrings.append(tr("Preserve case"));
QString description = tr("Flags: %1");
if (flagStrings.isEmpty())
description = description.arg(tr("None"));
......
......@@ -35,6 +35,7 @@
#include "searchresultcolor.h"
#include "ifindsupport.h"
#include "findplugin.h"
#include "treeviewfind.h"
#include <aggregation/aggregate.h>
......@@ -163,6 +164,14 @@ SearchResultWidget::SearchResultWidget(QWidget *parent) :
m_replaceButton->setText(tr("Replace"));
m_replaceButton->setToolButtonStyle(Qt::ToolButtonTextOnly);
m_replaceButton->setEnabled(false);
m_preserveCaseCheck = new QCheckBox(topWidget);
m_preserveCaseCheck->setText(tr("Preserve case"));
m_preserveCaseCheck->setEnabled(false);
if (FindPlugin * plugin = FindPlugin::instance()) {
m_preserveCaseCheck->setChecked(plugin->hasFindFlag(Find::FindPreserveCase));
connect(m_preserveCaseCheck, SIGNAL(clicked(bool)), plugin, SLOT(setPreserveCase(bool)));
}
m_matchesFoundLabel = new QLabel(topWidget);
updateMatchesFoundLabel();
......@@ -173,6 +182,7 @@ SearchResultWidget::SearchResultWidget(QWidget *parent) :
topLayout->addWidget(m_replaceLabel);
topLayout->addWidget(m_replaceTextEdit);
topLayout->addWidget(m_replaceButton);
topLayout->addWidget(m_preserveCaseCheck);
topLayout->addStretch(2);
topLayout->addWidget(m_matchesFoundLabel);
topWidget->setMinimumHeight(m_cancelButton->sizeHint().height()
......@@ -285,6 +295,7 @@ void SearchResultWidget::setShowReplaceUI(bool visible)
m_replaceLabel->setVisible(visible);
m_replaceTextEdit->setVisible(visible);
m_replaceButton->setVisible(visible);
m_preserveCaseCheck->setVisible(visible);
m_isShowingReplaceUI = visible;
}
......@@ -397,6 +408,7 @@ void SearchResultWidget::finishSearch(bool canceled)
m_sizeWarningOverridden = false;
m_replaceTextEdit->setEnabled(m_count > 0);
m_replaceButton->setEnabled(m_count > 0);
m_preserveCaseCheck->setEnabled(m_count > 0);
m_cancelButton->setVisible(false);
m_messageWidget->setVisible(canceled);
m_searchAgainButton->setVisible(m_searchAgainSupported);
......@@ -441,7 +453,7 @@ void SearchResultWidget::handleReplaceButton()
// by pressing return in replace line edit
if (m_replaceButton->isEnabled()) {
m_infoBar.clear();
emit replaceButtonClicked(m_replaceTextEdit->text(), checkedItems());
emit replaceButtonClicked(m_replaceTextEdit->text(), checkedItems(), m_preserveCaseCheck->isChecked());
}
}
......
......@@ -39,6 +39,7 @@
#include <QLineEdit>
#include <QToolButton>
#include <QWidget>
#include <QCheckBox>
namespace Find {
namespace Internal {
......@@ -94,7 +95,7 @@ public slots:
signals:
void activated(const Find::SearchResultItem &item);
void replaceButtonClicked(const QString &replaceText, const QList<Find::SearchResultItem> &checkedItems);
void replaceButtonClicked(const QString &replaceText, const QList<Find::SearchResultItem> &checkedItems, bool preserveCase);
void searchAgainRequested();
void cancelled();
void paused(bool paused);
......@@ -132,6 +133,7 @@ private:
QLineEdit *m_replaceTextEdit;
QToolButton *m_replaceButton;
QToolButton *m_searchAgainButton;
QCheckBox *m_preserveCaseCheck;
bool m_searchAgainSupported;
QWidget *m_descriptionContainer;
QLabel *m_label;
......
......@@ -218,7 +218,7 @@ using namespace Find::Internal;
*/
/*!
\fn void SearchResult::replaceButtonClicked(const QString &replaceText, const QList<Find::SearchResultItem> &checkedItems)
\fn void SearchResult::replaceButtonClicked(const QString &replaceText, const QList<Find::SearchResultItem> &checkedItems, bool preserveCase)
\brief Sent when the user initiated a replace, e.g. by pressing the replace
all button.
......@@ -614,8 +614,8 @@ SearchResult::SearchResult(SearchResultWidget *widget)
{
connect(widget, SIGNAL(activated(Find::SearchResultItem)),
this, SIGNAL(activated(Find::SearchResultItem)));
connect(widget, SIGNAL(replaceButtonClicked(QString,QList<Find::SearchResultItem>)),
this, SIGNAL(replaceButtonClicked(QString,QList<Find::SearchResultItem>)));
connect(widget, SIGNAL(replaceButtonClicked(QString,QList<Find::SearchResultItem>,bool)),
this, SIGNAL(replaceButtonClicked(QString,QList<Find::SearchResultItem>,bool)));
connect(widget, SIGNAL(cancelled()),
this, SIGNAL(cancelled()));
connect(widget, SIGNAL(paused(bool)),
......
......@@ -110,7 +110,7 @@ public slots:
signals:
void activated(const Find::SearchResultItem &item);
void replaceButtonClicked(const QString &replaceText, const QList<Find::SearchResultItem> &checkedItems);
void replaceButtonClicked(const QString &replaceText, const QList<Find::SearchResultItem> &checkedItems, bool preserveCase);
void cancelled();
void paused(bool paused);
void visibilityChanged(bool visible);
......
......@@ -59,6 +59,7 @@ const char REPLACE_ALL[] = "Find.ReplaceAll";
const char CASE_SENSITIVE[] = "Find.CaseSensitive";
const char WHOLE_WORDS[] = "Find.WholeWords";
const char REGULAR_EXPRESSIONS[] = "Find.RegularExpressions";
const char PRESERVE_CASE[] = "Find.PreserveCase";
const char TASK_SEARCH[] = "Find.Task.Search";
} // namespace Constants
......@@ -67,7 +68,8 @@ enum FindFlag {
FindBackward = 0x01,
FindCaseSensitively = 0x02,
FindWholeWords = 0x04,
FindRegularExpression = 0x08
FindRegularExpression = 0x08,
FindPreserveCase = 0x10
};
Q_DECLARE_FLAGS(FindFlags, FindFlag)
......
......@@ -933,8 +933,8 @@ void FindReferences::displayResults(int first, int last)
m_currentSearch = Find::SearchResultWindow::instance()->startNewSearch(
label, QString(), symbolName, Find::SearchResultWindow::SearchAndReplace);
m_currentSearch->setTextToReplace(replacement);
connect(m_currentSearch, SIGNAL(replaceButtonClicked(QString,QList<Find::SearchResultItem>)),
SLOT(onReplaceButtonClicked(QString,QList<Find::SearchResultItem>)));
connect(m_currentSearch, SIGNAL(replaceButtonClicked(QString,QList<Find::SearchResultItem>,bool)),
SLOT(onReplaceButtonClicked(QString,QList<Find::SearchResultItem>,bool)));
}
connect(m_currentSearch, SIGNAL(activated(Find::SearchResultItem)),
this, SLOT(openEditor(Find::SearchResultItem)));
......@@ -996,9 +996,9 @@ void FindReferences::openEditor(const Find::SearchResultItem &item)
}
}
void FindReferences::onReplaceButtonClicked(const QString &text, const QList<Find::SearchResultItem> &items)
void FindReferences::onReplaceButtonClicked(const QString &text, const QList<Find::SearchResultItem> &items, bool preserveCase)
{
const QStringList fileNames = TextEditor::BaseFileFind::replaceAll(text, items);
const QStringList fileNames = TextEditor::BaseFileFind::replaceAll(text, items, preserveCase);
// files that are opened in an editor are changed, but not saved
QStringList changedOnDisk;
......
......@@ -86,7 +86,7 @@ private Q_SLOTS:
void cancel();
void setPaused(bool paused);
void openEditor(const Find::SearchResultItem &item);
void onReplaceButtonClicked(const QString &text, const QList<Find::SearchResultItem> &items);
void onReplaceButtonClicked(const QString &text, const QList<Find::SearchResultItem> &items, bool preserveCase);
private:
QPointer<Find::SearchResult> m_currentSearch;
......
......@@ -131,8 +131,8 @@ void BaseFileFind::runNewSearch(const QString &txt, Find::FindFlags findFlags,
search->setUserData(qVariantFromValue(parameters));
connect(search, SIGNAL(activated(Find::SearchResultItem)), this, SLOT(openEditor(Find::SearchResultItem)));
if (searchMode == SearchResultWindow::SearchAndReplace) {
connect(search, SIGNAL(replaceButtonClicked(QString,QList<Find::SearchResultItem>)),
this, SLOT(doReplace(QString,QList<Find::SearchResultItem>)));
connect(search, SIGNAL(replaceButtonClicked(QString,QList<Find::SearchResultItem>,bool)),
this, SLOT(doReplace(QString,QList<Find::SearchResultItem>,bool)));
}
connect(search, SIGNAL(visibilityChanged(bool)), this, SLOT(hideHighlightAll(bool)));
connect(search, SIGNAL(cancelled()), this, SLOT(cancel()));
......@@ -183,9 +183,10 @@ void BaseFileFind::replaceAll(const QString &txt, Find::FindFlags findFlags)
}
void BaseFileFind::doReplace(const QString &text,
const QList<Find::SearchResultItem> &items)
const QList<Find::SearchResultItem> &items,
bool preserveCase)
{
QStringList files = replaceAll(text, items);
QStringList files = replaceAll(text, items, preserveCase);
if (!files.isEmpty()) {
Core::DocumentManager::notifyFilesChangedInternally(files);
Find::SearchResultWindow::instance()->hide();
......@@ -331,7 +332,8 @@ void BaseFileFind::searchAgain()
}
QStringList BaseFileFind::replaceAll(const QString &text,
const QList<Find::SearchResultItem> &items)
const QList<Find::SearchResultItem> &items,
bool preserveCase)
{
if (items.isEmpty())
return QStringList();
......@@ -358,10 +360,15 @@ QStringList BaseFileFind::replaceAll(const QString &text,
processed.insert(p);
QString replacement;
if (item.userData.canConvert<QStringList>() && !item.userData.toStringList().isEmpty())
if (item.userData.canConvert<QStringList>() && !item.userData.toStringList().isEmpty()) {
replacement = Utils::expandRegExpReplacement(text, item.userData.toStringList());
else
} else if (preserveCase) {
const QString originalText = (item.textMarkLength == 0) ? item.text
: item.text.mid(item.textMarkPos, item.textMarkLength);
replacement = Utils::matchCaseReplacement(originalText, text);
} else {
replacement = text;
}
const int start = file->position(item.lineNumber, item.textMarkPos + 1);
const int end = file->position(item.lineNumber,
......
......@@ -71,7 +71,8 @@ public:
/* returns the list of unique files that were passed in items */
static QStringList replaceAll(const QString &txt,
const QList<Find::SearchResultItem> &items);
const QList<Find::SearchResultItem> &items,
bool preserveCase = false);
protected:
virtual Utils::FileIterator *files(const QStringList &nameFilters,
......@@ -95,7 +96,8 @@ private slots:
void setPaused(bool paused);
void openEditor(const Find::SearchResultItem &item);
void doReplace(const QString &txt,
const QList<Find::SearchResultItem> &items);
const QList<Find::SearchResultItem> &items,
bool preserveCase);
void hideHighlightAll(bool visible);
void searchAgain();
......
......@@ -51,6 +51,7 @@ private slots:
void multipleResults();
void caseSensitive();
void caseInSensitive();
void matchCaseReplacement();
};
namespace {
......@@ -99,6 +100,49 @@ void tst_FileSearch::caseInSensitive()
test_helper(expectedResults, QLatin1String("CaseSensitive"), QTextDocument::FindFlags(0));
}
void tst_FileSearch::matchCaseReplacement()
{
QCOMPARE(Utils::matchCaseReplacement("", "foobar"), QString("foobar")); //empty string
QCOMPARE(Utils::matchCaseReplacement("testpad", "foobar"), QString("foobar")); //lower case
QCOMPARE(Utils::matchCaseReplacement("TESTPAD", "foobar"), QString("FOOBAR")); //upper case
QCOMPARE(Utils::matchCaseReplacement("Testpad", "foobar"), QString("Foobar")); //capitalized
QCOMPARE(Utils::matchCaseReplacement("tESTPAD", "foobar"), QString("fOOBAR")); //un-capitalized
QCOMPARE(Utils::matchCaseReplacement("tEsTpAd", "foobar"), QString("foobar")); //mixed case, use replacement as specified
QCOMPARE(Utils::matchCaseReplacement("TeStPaD", "foobar"), QString("foobar")); //mixed case, use replacement as specified
QCOMPARE(Utils::matchCaseReplacement("testpad", "fooBar"), QString("foobar")); //lower case
QCOMPARE(Utils::matchCaseReplacement("TESTPAD", "fooBar"), QString("FOOBAR")); //upper case
QCOMPARE(Utils::matchCaseReplacement("Testpad", "fooBar"), QString("Foobar")); //capitalized
QCOMPARE(Utils::matchCaseReplacement("tESTPAD", "fooBar"), QString("fOOBAR")); //un-capitalized
QCOMPARE(Utils::matchCaseReplacement("tEsTpAd", "fooBar"), QString("fooBar")); //mixed case, use replacement as specified
QCOMPARE(Utils::matchCaseReplacement("TeStPaD", "fooBar"), QString("fooBar")); //mixed case, use replacement as specified
//with common prefix
QCOMPARE(Utils::matchCaseReplacement("pReFiXtestpad", "prefixfoobar"), QString("pReFiXfoobar")); //lower case
QCOMPARE(Utils::matchCaseReplacement("pReFiXTESTPAD", "prefixfoobar"), QString("pReFiXFOOBAR")); //upper case
QCOMPARE(Utils::matchCaseReplacement("pReFiXTestpad", "prefixfoobar"), QString("pReFiXFoobar")); //capitalized
QCOMPARE(Utils::matchCaseReplacement("pReFiXtESTPAD", "prefixfoobar"), QString("pReFiXfOOBAR")); //un-capitalized
QCOMPARE(Utils::matchCaseReplacement("pReFiXtEsTpAd", "prefixfoobar"), QString("pReFiXfoobar")); //mixed case, use replacement as specified
QCOMPARE(Utils::matchCaseReplacement("pReFiXTeStPaD", "prefixfoobar"), QString("pReFiXfoobar")); //mixed case, use replacement as specified
//with common suffix
QCOMPARE(Utils::matchCaseReplacement("testpadSuFfIx", "foobarsuffix"), QString("foobarSuFfIx")); //lower case
QCOMPARE(Utils::matchCaseReplacement("TESTPADSuFfIx", "foobarsuffix"), QString("FOOBARSuFfIx")); //upper case
QCOMPARE(Utils::matchCaseReplacement("TestpadSuFfIx", "foobarsuffix"), QString("FoobarSuFfIx")); //capitalized
QCOMPARE(Utils::matchCaseReplacement("tESTPADSuFfIx", "foobarsuffix"), QString("fOOBARSuFfIx")); //un-capitalized
QCOMPARE(Utils::matchCaseReplacement("tEsTpAdSuFfIx", "foobarsuffix"), QString("foobarSuFfIx")); //mixed case, use replacement as specified