diff --git a/CMakeLists.txt b/CMakeLists.txt index 2069950..b4a39fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,11 +37,13 @@ target_sources(pineapple-notepad PRIVATE main.cpp mainwindow.cpp mainwindow.h + findreplacedialog.cpp findreplacedialog.h sciedit.cpp sciedit.h tabwidget.cpp tabwidget.h documentmanager.cpp documentmanager.h editorviewhelper.cpp editorviewhelper.h generalsettings.ui + findreplacedialog.ui appsettings.kcfg ) diff --git a/findreplacedialog.cpp b/findreplacedialog.cpp new file mode 100644 index 0000000..177cf76 --- /dev/null +++ b/findreplacedialog.cpp @@ -0,0 +1,325 @@ +#include "findreplacedialog.h" + +#include "ui_findreplacedialog.h" + +#include +#include +#include +#include +#include +#include + +static QString decodeExtended(const QString &s) +{ + QString out; + out.reserve(s.size()); + + for (int i = 0; i < s.size(); ++i) { + const QChar c = s.at(i); + if (c != QLatin1Char('\\') || i + 1 >= s.size()) { + out.append(c); + continue; + } + + const QChar n = s.at(i + 1); + if (n == QLatin1Char('n')) { + out.append(QLatin1Char('\n')); + ++i; + } else if (n == QLatin1Char('r')) { + out.append(QLatin1Char('\r')); + ++i; + } else if (n == QLatin1Char('t')) { + out.append(QLatin1Char('\t')); + ++i; + } else if (n == QLatin1Char('\\')) { + out.append(QLatin1Char('\\')); + ++i; + } else if (n == QLatin1Char('0')) { + out.append(QChar(0)); + ++i; + } else if (n == QLatin1Char('x') && i + 3 < s.size()) { + bool ok = false; + const QString hex = s.mid(i + 2, 2); + const int code = hex.toInt(&ok, 16); + if (ok) { + out.append(QChar(code)); + i += 3; + } else { + out.append(c); + } + } else { + out.append(n); + ++i; + } + } + + return out; +} + +FindReplaceDialog::FindReplaceDialog(QWidget *parent) + : QDialog(parent) + , ui(new Ui::FindReplaceDialog) +{ + ui->setupUi(this); + setModal(false); + setWindowFlags(windowFlags() | Qt::Tool); + + initCombos(); + initModeCombos(); + initSync(); + + connect(ui->tabWidget, &QTabWidget::currentChanged, this, [this](int index) { + Q_UNUSED(index) + if (ui->tabWidget->currentWidget() == ui->findTab) { + syncFromReplaceTab(); + } else { + syncFromFindTab(); + } + }); + + connect(ui->findNextBtnFind, &QPushButton::clicked, this, [this]() { + commitHistory(); + emit findRequested(true); + }); + connect(ui->findPrevBtnFind, &QPushButton::clicked, this, [this]() { + commitHistory(); + emit findRequested(false); + }); + connect(ui->closeBtnFind, &QPushButton::clicked, this, &QDialog::hide); + + connect(ui->findNextBtnReplace, &QPushButton::clicked, this, [this]() { + commitHistory(); + emit findRequested(true); + }); + connect(ui->replaceBtn, &QPushButton::clicked, this, [this]() { + commitHistory(); + emit replaceRequested(); + }); + connect(ui->replaceAllBtn, &QPushButton::clicked, this, [this]() { + commitHistory(); + emit replaceAllRequested(); + }); + connect(ui->closeBtnReplace, &QPushButton::clicked, this, &QDialog::hide); + + resize(520, sizeHint().height()); +} + +FindReplaceDialog::~FindReplaceDialog() +{ + delete ui; +} + +void FindReplaceDialog::initCombos() +{ + ui->findWhatComboFind->setInsertPolicy(QComboBox::NoInsert); + ui->findWhatComboReplace->setInsertPolicy(QComboBox::NoInsert); + ui->replaceWithComboReplace->setInsertPolicy(QComboBox::NoInsert); +} + +void FindReplaceDialog::initModeCombos() +{ + ui->modeComboFind->addItem(QStringLiteral("Normal"), QVariant::fromValue(static_cast(SearchMode::Normal))); + ui->modeComboFind->addItem(QStringLiteral("Extended (\\n, \\r, \\t, \\0, \\xNN)"), QVariant::fromValue(static_cast(SearchMode::Extended))); + ui->modeComboFind->addItem(QStringLiteral("Regular expression"), QVariant::fromValue(static_cast(SearchMode::Regex))); + + ui->modeComboReplace->addItem(QStringLiteral("Normal"), QVariant::fromValue(static_cast(SearchMode::Normal))); + ui->modeComboReplace->addItem(QStringLiteral("Extended (\\n, \\r, \\t, \\0, \\xNN)"), QVariant::fromValue(static_cast(SearchMode::Extended))); + ui->modeComboReplace->addItem(QStringLiteral("Regular expression"), QVariant::fromValue(static_cast(SearchMode::Regex))); +} + +void FindReplaceDialog::initSync() +{ + if (ui->findWhatComboFind->lineEdit()) { + connect(ui->findWhatComboFind->lineEdit(), &QLineEdit::textEdited, this, [this]() { syncFromFindTab(); }); + } + connect(ui->matchCaseCheckFind, &QCheckBox::toggled, this, [this]() { syncFromFindTab(); }); + connect(ui->wholeWordCheckFind, &QCheckBox::toggled, this, [this]() { syncFromFindTab(); }); + connect(ui->wrapAroundCheckFind, &QCheckBox::toggled, this, [this]() { syncFromFindTab(); }); + connect(ui->inSelectionCheckFind, &QCheckBox::toggled, this, [this]() { syncFromFindTab(); }); + connect(ui->modeComboFind, &QComboBox::currentIndexChanged, this, [this](int) { syncFromFindTab(); }); + + if (ui->findWhatComboReplace->lineEdit()) { + connect(ui->findWhatComboReplace->lineEdit(), &QLineEdit::textEdited, this, [this]() { syncFromReplaceTab(); }); + } + if (ui->replaceWithComboReplace->lineEdit()) { + connect(ui->replaceWithComboReplace->lineEdit(), &QLineEdit::textEdited, this, [this]() { syncFromReplaceTab(); }); + } + connect(ui->matchCaseCheckReplace, &QCheckBox::toggled, this, [this]() { syncFromReplaceTab(); }); + connect(ui->wholeWordCheckReplace, &QCheckBox::toggled, this, [this]() { syncFromReplaceTab(); }); + connect(ui->wrapAroundCheckReplace, &QCheckBox::toggled, this, [this]() { syncFromReplaceTab(); }); + connect(ui->inSelectionCheckReplace, &QCheckBox::toggled, this, [this]() { syncFromReplaceTab(); }); + connect(ui->modeComboReplace, &QComboBox::currentIndexChanged, this, [this](int) { syncFromReplaceTab(); }); + + syncFromFindTab(); +} + +void FindReplaceDialog::syncFromFindTab() +{ + if (m_syncing) { + return; + } + m_syncing = true; + ui->findWhatComboReplace->setCurrentText(ui->findWhatComboFind->currentText()); + ui->matchCaseCheckReplace->setChecked(ui->matchCaseCheckFind->isChecked()); + ui->wholeWordCheckReplace->setChecked(ui->wholeWordCheckFind->isChecked()); + ui->wrapAroundCheckReplace->setChecked(ui->wrapAroundCheckFind->isChecked()); + ui->inSelectionCheckReplace->setChecked(ui->inSelectionCheckFind->isChecked()); + ui->modeComboReplace->setCurrentIndex(ui->modeComboFind->currentIndex()); + m_syncing = false; +} + +void FindReplaceDialog::syncFromReplaceTab() +{ + if (m_syncing) { + return; + } + m_syncing = true; + ui->findWhatComboFind->setCurrentText(ui->findWhatComboReplace->currentText()); + ui->matchCaseCheckFind->setChecked(ui->matchCaseCheckReplace->isChecked()); + ui->wholeWordCheckFind->setChecked(ui->wholeWordCheckReplace->isChecked()); + ui->wrapAroundCheckFind->setChecked(ui->wrapAroundCheckReplace->isChecked()); + ui->inSelectionCheckFind->setChecked(ui->inSelectionCheckReplace->isChecked()); + ui->modeComboFind->setCurrentIndex(ui->modeComboReplace->currentIndex()); + m_syncing = false; +} + +void FindReplaceDialog::openFind() +{ + ui->tabWidget->setCurrentWidget(ui->findTab); + show(); + raise(); + activateWindow(); + ui->findWhatComboFind->setFocus(); + if (ui->findWhatComboFind->lineEdit()) { + ui->findWhatComboFind->lineEdit()->selectAll(); + } +} + +void FindReplaceDialog::openReplace() +{ + ui->tabWidget->setCurrentWidget(ui->replaceTab); + show(); + raise(); + activateWindow(); + ui->findWhatComboReplace->setFocus(); + if (ui->findWhatComboReplace->lineEdit()) { + ui->findWhatComboReplace->lineEdit()->selectAll(); + } +} + +QString FindReplaceDialog::findText() const +{ + if (ui->tabWidget->currentWidget() == ui->replaceTab) { + return ui->findWhatComboReplace->currentText(); + } + return ui->findWhatComboFind->currentText(); +} + +QString FindReplaceDialog::replaceText() const +{ + return ui->replaceWithComboReplace->currentText(); +} + +QString FindReplaceDialog::findTextForSearch() const +{ + const QString raw = findText(); + if (searchMode() == SearchMode::Extended) { + return decodeExtended(raw); + } + return raw; +} + +QString FindReplaceDialog::replaceTextForReplace() const +{ + const QString raw = replaceText(); + if (searchMode() == SearchMode::Extended) { + return decodeExtended(raw); + } + return raw; +} + +bool FindReplaceDialog::matchCase() const +{ + if (ui->tabWidget->currentWidget() == ui->replaceTab) { + return ui->matchCaseCheckReplace->isChecked(); + } + return ui->matchCaseCheckFind->isChecked(); +} + +bool FindReplaceDialog::wholeWord() const +{ + if (ui->tabWidget->currentWidget() == ui->replaceTab) { + return ui->wholeWordCheckReplace->isChecked(); + } + return ui->wholeWordCheckFind->isChecked(); +} + +bool FindReplaceDialog::wrapAround() const +{ + if (ui->tabWidget->currentWidget() == ui->replaceTab) { + return ui->wrapAroundCheckReplace->isChecked(); + } + return ui->wrapAroundCheckFind->isChecked(); +} + +bool FindReplaceDialog::inSelection() const +{ + if (ui->tabWidget->currentWidget() == ui->replaceTab) { + return ui->inSelectionCheckReplace->isChecked(); + } + return ui->inSelectionCheckFind->isChecked(); +} + +SearchMode FindReplaceDialog::searchMode() const +{ + const int v = (ui->tabWidget->currentWidget() == ui->replaceTab) ? ui->modeComboReplace->currentData().toInt() + : ui->modeComboFind->currentData().toInt(); + return static_cast(v); +} + +void FindReplaceDialog::setFindTextIfNotEmpty(const QString &text) +{ + if (text.isEmpty()) { + return; + } + ui->findWhatComboFind->setCurrentText(text); + ui->findWhatComboReplace->setCurrentText(text); + if (ui->tabWidget->currentWidget() == ui->replaceTab) { + if (ui->findWhatComboReplace->lineEdit()) { + ui->findWhatComboReplace->lineEdit()->selectAll(); + } + } else { + if (ui->findWhatComboFind->lineEdit()) { + ui->findWhatComboFind->lineEdit()->selectAll(); + } + } +} + +void FindReplaceDialog::commitHistory() +{ + const auto commit = [](QComboBox *combo) { + const QString v = combo->currentText(); + if (v.isEmpty()) { + return; + } + const int idx = combo->findText(v); + if (idx >= 0) { + combo->removeItem(idx); + } + combo->insertItem(0, v); + combo->setCurrentIndex(0); + while (combo->count() > 20) { + combo->removeItem(combo->count() - 1); + } + }; + + commit(ui->findWhatComboFind); + commit(ui->findWhatComboReplace); + commit(ui->replaceWithComboReplace); +} + +void FindReplaceDialog::closeEvent(QCloseEvent *event) +{ + hide(); + event->ignore(); +} diff --git a/findreplacedialog.h b/findreplacedialog.h new file mode 100644 index 0000000..30066dd --- /dev/null +++ b/findreplacedialog.h @@ -0,0 +1,57 @@ +#pragma once + +#include + +namespace Ui { +class FindReplaceDialog; +} + +enum class SearchMode { + Normal, + Extended, + Regex, +}; + +class FindReplaceDialog final : public QDialog +{ + Q_OBJECT + +public: + explicit FindReplaceDialog(QWidget *parent = nullptr); + ~FindReplaceDialog() override; + + void openFind(); + void openReplace(); + + QString findText() const; + QString replaceText() const; + QString findTextForSearch() const; + QString replaceTextForReplace() const; + + bool matchCase() const; + bool wholeWord() const; + bool wrapAround() const; + bool inSelection() const; + SearchMode searchMode() const; + + void setFindTextIfNotEmpty(const QString &text); + void commitHistory(); + +signals: + void findRequested(bool forward); + void replaceRequested(); + void replaceAllRequested(); + +protected: + void closeEvent(QCloseEvent *event) override; + +private: + void initCombos(); + void initModeCombos(); + void initSync(); + void syncFromFindTab(); + void syncFromReplaceTab(); + + Ui::FindReplaceDialog *ui = nullptr; + bool m_syncing = false; +}; diff --git a/findreplacedialog.ui b/findreplacedialog.ui new file mode 100644 index 0000000..22598b5 --- /dev/null +++ b/findreplacedialog.ui @@ -0,0 +1,317 @@ + + + FindReplaceDialog + + + Find / Replace + + + + + + 0 + + + + Find + + + + + + + + + + Find what: + + + + + + + true + + + + + + + + + Search options + + + + + + Match case + + + + + + + Match whole word only + + + + + + + Wrap around + + + true + + + + + + + In selection + + + + + + + Search mode: + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + 0 + 0 + + + + + + + Find Next + + + + + + + Find Previous + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Close + + + + + + + + + + + Replace + + + + + + + + + + Find what: + + + + + + + true + + + + + + + Replace with: + + + + + + + true + + + + + + + + + Search options + + + + + + Match case + + + + + + + Match whole word only + + + + + + + Wrap around + + + true + + + + + + + In selection + + + + + + + Search mode: + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + 0 + 0 + + + + + + + Find Next + + + + + + + Replace + + + + + + + Replace All + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Close + + + + + + + + + + + + + + + diff --git a/mainwindow.cpp b/mainwindow.cpp index a3cca80..efd21d6 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -5,12 +5,25 @@ #include "documentmanager.h" #include "appsettings.h" #include "editorviewhelper.h" +#include "findreplacedialog.h" #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include #include +#include +#include #include #include @@ -98,11 +111,18 @@ void MainWindow::setupActions() }, actionCollection()); // "Search" menu - KStandardAction::find(this, [](){}, actionCollection()); - KStandardAction::findNext(this, [](){}, actionCollection()); - KStandardAction::findPrev(this, [](){}, actionCollection()); - KStandardAction::replace(this, [](){}, actionCollection()); - qDebug() << KStandardAction::name(KStandardAction::Replace); + KStandardAction::find(this, [this](){ + showFindReplaceDialog(false); + }, actionCollection()); + KStandardAction::findNext(this, [this](){ + findNextInCurrentEditor(true, true); + }, actionCollection()); + KStandardAction::findPrev(this, [this](){ + findNextInCurrentEditor(false, true); + }, actionCollection()); + KStandardAction::replace(this, [this](){ + showFindReplaceDialog(true); + }, actionCollection()); // "Language" menu QAction *lexerNoneAction = new QAction(this); @@ -378,3 +398,326 @@ void MainWindow::updateActions() indentGuideAction->setChecked(m_indentGuidesEnabled); } } + +void MainWindow::showFindReplaceDialog(bool showReplace) +{ + if (!m_findReplaceDialog) { + m_findReplaceDialog = new FindReplaceDialog(this); + connect(m_findReplaceDialog, &FindReplaceDialog::findRequested, this, [this](bool forward) { + findNextInCurrentEditor(forward, true); + }); + connect(m_findReplaceDialog, &FindReplaceDialog::replaceRequested, this, [this]() { + replaceOneInCurrentEditor(true); + }); + connect(m_findReplaceDialog, &FindReplaceDialog::replaceAllRequested, this, [this]() { + replaceAllInCurrentEditor(true); + }); + } + + if (SciEdit *editor = m_tabWidget->currentEditor()) { + const int selStart = static_cast(editor->selectionStart()); + const int selEnd = static_cast(editor->selectionEnd()); + if (selStart != selEnd) { + const int start = qMin(selStart, selEnd); + const int end = qMax(selStart, selEnd); + const QByteArray selected = editor->textRange(start, end); + m_findReplaceDialog->setFindTextIfNotEmpty(QString::fromUtf8(selected)); + } + } + + if (showReplace) { + m_findReplaceDialog->openReplace(); + } else { + m_findReplaceDialog->openFind(); + } +} + +static sptr_t buildSearchFlags(const FindReplaceDialog *dialog) +{ + sptr_t flags = 0; + if (dialog->matchCase()) { + flags |= SCFIND_MATCHCASE; + } + if (dialog->wholeWord()) { + flags |= SCFIND_WHOLEWORD; + } + if (dialog->searchMode() == SearchMode::Regex) { + flags |= SCFIND_REGEXP; + } + return flags; +} + +static QString buildFindPattern(const FindReplaceDialog *dialog) +{ + return dialog->findTextForSearch(); +} + +static QString buildReplacementText(const FindReplaceDialog *dialog) +{ + return dialog->replaceTextForReplace(); +} + +bool MainWindow::findNextInCurrentEditor(bool forward, bool showNotFoundMessage) +{ + SciEdit *editor = m_tabWidget->currentEditor(); + if (!editor) { + return false; + } + + if (!m_findReplaceDialog) { + m_findReplaceDialog = new FindReplaceDialog(this); + connect(m_findReplaceDialog, &FindReplaceDialog::findRequested, this, [this](bool forward) { + findNextInCurrentEditor(forward, true); + }); + connect(m_findReplaceDialog, &FindReplaceDialog::replaceRequested, this, [this]() { + replaceOneInCurrentEditor(true); + }); + connect(m_findReplaceDialog, &FindReplaceDialog::replaceAllRequested, this, [this]() { + replaceAllInCurrentEditor(true); + }); + } + + const QString needle = buildFindPattern(m_findReplaceDialog); + if (needle.isEmpty()) { + return false; + } + + const QByteArray needleBytes = needle.toUtf8(); + const sptr_t flags = buildSearchFlags(m_findReplaceDialog); + + const int docLen = static_cast(editor->textLength()); + int rangeStart = 0; + int rangeEnd = docLen; + + if (m_findReplaceDialog->inSelection()) { + if (!m_findInSelectionEnabled) { + const int selStart = static_cast(editor->selectionStart()); + const int selEnd = static_cast(editor->selectionEnd()); + if (selStart != selEnd) { + m_findSelectionStart = qMin(selStart, selEnd); + m_findSelectionEnd = qMax(selStart, selEnd); + m_findInSelectionEnabled = true; + } + } + } else { + m_findInSelectionEnabled = false; + } + + if (m_findInSelectionEnabled) { + rangeStart = qBound(0, m_findSelectionStart, docLen); + rangeEnd = qBound(0, m_findSelectionEnd, docLen); + if (rangeStart >= rangeEnd) { + m_findInSelectionEnabled = false; + rangeStart = 0; + rangeEnd = docLen; + } + } + + const int selStartNow = static_cast(editor->selectionStart()); + const int selEndNow = static_cast(editor->selectionEnd()); + int caretBase = static_cast(editor->currentPos()); + if (forward && selStartNow != selEndNow) { + caretBase = qMax(selStartNow, selEndNow); + } + caretBase = qBound(rangeStart, caretBase, rangeEnd); + + auto selectMatch = [&](int start, int end) { + editor->setSel(start, end); + editor->scrollCaret(); + }; + + auto showNotFound = [&]() { + if (!showNotFoundMessage) { + return; + } + QMessageBox::information(this, QStringLiteral("Find"), QStringLiteral("Text not found.")); + }; + + if (forward) { + const QPair found = editor->findText(static_cast(flags), needleBytes.constData(), caretBase, rangeEnd); + if (found.first >= 0) { + selectMatch(found.first, found.second); + return true; + } + if (m_findReplaceDialog->wrapAround()) { + const QPair wrapped = editor->findText(static_cast(flags), needleBytes.constData(), rangeStart, rangeEnd); + if (wrapped.first >= 0) { + selectMatch(wrapped.first, wrapped.second); + return true; + } + } + showNotFound(); + return false; + } + + auto findLastBefore = [&](int endExclusive) -> QPair { + int bestStart = -1; + int bestEnd = -1; + int scanPos = rangeStart; + while (scanPos < endExclusive) { + const QPair found = editor->findText(static_cast(flags), needleBytes.constData(), scanPos, endExclusive); + if (found.first < 0 || found.first >= endExclusive) { + break; + } + bestStart = found.first; + bestEnd = found.second; + scanPos = found.first + 1; + } + return QPair(bestStart, bestEnd); + }; + + QPair found = findLastBefore(caretBase); + if (found.first >= 0) { + selectMatch(found.first, found.second); + return true; + } + + if (m_findReplaceDialog->wrapAround()) { + found = findLastBefore(rangeEnd); + if (found.first >= 0) { + selectMatch(found.first, found.second); + return true; + } + } + + showNotFound(); + return false; +} + +bool MainWindow::replaceOneInCurrentEditor(bool showNotFoundMessage) +{ + SciEdit *editor = m_tabWidget->currentEditor(); + if (!editor) { + return false; + } + if (!m_findReplaceDialog) { + m_findReplaceDialog = new FindReplaceDialog(this); + connect(m_findReplaceDialog, &FindReplaceDialog::findRequested, this, [this](bool forward) { + findNextInCurrentEditor(forward, true); + }); + connect(m_findReplaceDialog, &FindReplaceDialog::replaceRequested, this, [this]() { + replaceOneInCurrentEditor(true); + }); + connect(m_findReplaceDialog, &FindReplaceDialog::replaceAllRequested, this, [this]() { + replaceAllInCurrentEditor(true); + }); + } + + const QString needle = buildFindPattern(m_findReplaceDialog); + if (needle.isEmpty()) { + return false; + } + + if (!findNextInCurrentEditor(true, showNotFoundMessage)) { + return false; + } + + const QString replacement = buildReplacementText(m_findReplaceDialog); + const QByteArray replacementBytes = replacement.toUtf8(); + + editor->targetFromSelection(); + if (m_findReplaceDialog->searchMode() == SearchMode::Regex) { + editor->replaceTargetRE(replacementBytes.size(), replacementBytes.constData()); + } else { + editor->replaceTarget(replacementBytes.size(), replacementBytes.constData()); + } + + findNextInCurrentEditor(true, false); + return true; +} + +int MainWindow::replaceAllInCurrentEditor(bool showNotFoundMessage) +{ + SciEdit *editor = m_tabWidget->currentEditor(); + if (!editor) { + return 0; + } + if (!m_findReplaceDialog) { + m_findReplaceDialog = new FindReplaceDialog(this); + connect(m_findReplaceDialog, &FindReplaceDialog::findRequested, this, [this](bool forward) { + findNextInCurrentEditor(forward, true); + }); + connect(m_findReplaceDialog, &FindReplaceDialog::replaceRequested, this, [this]() { + replaceOneInCurrentEditor(true); + }); + connect(m_findReplaceDialog, &FindReplaceDialog::replaceAllRequested, this, [this]() { + replaceAllInCurrentEditor(true); + }); + } + + const QString needle = buildFindPattern(m_findReplaceDialog); + if (needle.isEmpty()) { + return 0; + } + + const QByteArray needleBytes = needle.toUtf8(); + const QString replacement = buildReplacementText(m_findReplaceDialog); + const QByteArray replacementBytes = replacement.toUtf8(); + const sptr_t flags = buildSearchFlags(m_findReplaceDialog); + + const int docLen = static_cast(editor->textLength()); + int rangeStart = 0; + int rangeEnd = docLen; + + if (m_findReplaceDialog->inSelection()) { + if (!m_findInSelectionEnabled) { + const int selStart = static_cast(editor->selectionStart()); + const int selEnd = static_cast(editor->selectionEnd()); + if (selStart != selEnd) { + m_findSelectionStart = qMin(selStart, selEnd); + m_findSelectionEnd = qMax(selStart, selEnd); + m_findInSelectionEnabled = true; + } + } + } else { + m_findInSelectionEnabled = false; + } + + if (m_findInSelectionEnabled) { + rangeStart = qBound(0, m_findSelectionStart, docLen); + rangeEnd = qBound(0, m_findSelectionEnd, docLen); + if (rangeStart >= rangeEnd) { + m_findInSelectionEnabled = false; + rangeStart = 0; + rangeEnd = docLen; + } + } + + editor->setSearchFlags(flags); + + int replacedCount = 0; + int start = rangeStart; + int end = rangeEnd; + + editor->beginUndoAction(); + while (start <= end) { + editor->setTargetRange(start, end); + const sptr_t pos = editor->searchInTarget(needleBytes.size(), needleBytes.constData()); + if (pos < 0) { + break; + } + + const int matchStart = static_cast(editor->targetStart()); + const int matchEnd = static_cast(editor->targetEnd()); + const int matchLen = matchEnd - matchStart; + + sptr_t replacedLen = 0; + if (m_findReplaceDialog->searchMode() == SearchMode::Regex) { + replacedLen = editor->replaceTargetRE(replacementBytes.size(), replacementBytes.constData()); + } else { + replacedLen = editor->replaceTarget(replacementBytes.size(), replacementBytes.constData()); + } + + ++replacedCount; + const int delta = static_cast(replacedLen) - matchLen; + end += delta; + start = matchStart + static_cast(replacedLen); + } + editor->endUndoAction(); + + if (replacedCount == 0 && showNotFoundMessage) { + QMessageBox::information(this, QStringLiteral("Replace"), QStringLiteral("Text not found.")); + } + + return replacedCount; +} diff --git a/mainwindow.h b/mainwindow.h index dc98f44..2877c1b 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -8,6 +8,7 @@ class QLabel; class SciEdit; class TabWidget; class DocumentManager; +class FindReplaceDialog; class MainWindow : public KXmlGuiWindow { @@ -36,6 +37,10 @@ private: void updateWindowTitle(); void updateStatusBar(); void updateActions(); + void showFindReplaceDialog(bool showReplace); + bool findNextInCurrentEditor(bool forward, bool showNotFoundMessage); + bool replaceOneInCurrentEditor(bool showNotFoundMessage); + int replaceAllInCurrentEditor(bool showNotFoundMessage); DocumentManager *m_documentManager; TabWidget *m_tabWidget; @@ -43,6 +48,10 @@ private: QLabel *m_encodingStatusLabel; QLabel *m_languageStatusLabel; bool m_indentGuidesEnabled = false; + FindReplaceDialog *m_findReplaceDialog = nullptr; + bool m_findInSelectionEnabled = false; + int m_findSelectionStart = 0; + int m_findSelectionEnd = 0; }; // plainly for KConfigDialog