find and replace dialog

This commit is contained in:
2026-02-02 00:02:43 +08:00
parent 5f8aa70827
commit e1c55eae84
6 changed files with 1058 additions and 5 deletions

View File

@@ -37,11 +37,13 @@ target_sources(pineapple-notepad
PRIVATE PRIVATE
main.cpp main.cpp
mainwindow.cpp mainwindow.h mainwindow.cpp mainwindow.h
findreplacedialog.cpp findreplacedialog.h
sciedit.cpp sciedit.h sciedit.cpp sciedit.h
tabwidget.cpp tabwidget.h tabwidget.cpp tabwidget.h
documentmanager.cpp documentmanager.h documentmanager.cpp documentmanager.h
editorviewhelper.cpp editorviewhelper.h editorviewhelper.cpp editorviewhelper.h
generalsettings.ui generalsettings.ui
findreplacedialog.ui
appsettings.kcfg appsettings.kcfg
) )

325
findreplacedialog.cpp Normal file
View File

@@ -0,0 +1,325 @@
#include "findreplacedialog.h"
#include "ui_findreplacedialog.h"
#include <QCheckBox>
#include <QCloseEvent>
#include <QComboBox>
#include <QLineEdit>
#include <QPushButton>
#include <QTabWidget>
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<int>(SearchMode::Normal)));
ui->modeComboFind->addItem(QStringLiteral("Extended (\\n, \\r, \\t, \\0, \\xNN)"), QVariant::fromValue(static_cast<int>(SearchMode::Extended)));
ui->modeComboFind->addItem(QStringLiteral("Regular expression"), QVariant::fromValue(static_cast<int>(SearchMode::Regex)));
ui->modeComboReplace->addItem(QStringLiteral("Normal"), QVariant::fromValue(static_cast<int>(SearchMode::Normal)));
ui->modeComboReplace->addItem(QStringLiteral("Extended (\\n, \\r, \\t, \\0, \\xNN)"), QVariant::fromValue(static_cast<int>(SearchMode::Extended)));
ui->modeComboReplace->addItem(QStringLiteral("Regular expression"), QVariant::fromValue(static_cast<int>(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<SearchMode>(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();
}

57
findreplacedialog.h Normal file
View File

@@ -0,0 +1,57 @@
#pragma once
#include <QDialog>
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;
};

317
findreplacedialog.ui Normal file
View File

@@ -0,0 +1,317 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FindReplaceDialog</class>
<widget class="QDialog" name="FindReplaceDialog">
<property name="windowTitle">
<string>Find / Replace</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="findTab">
<attribute name="title">
<string>Find</string>
</attribute>
<layout class="QHBoxLayout" name="findTabLayout">
<item>
<layout class="QVBoxLayout" name="findLeftLayout">
<item>
<layout class="QFormLayout" name="findFormLayout">
<item row="0" column="0">
<widget class="QLabel" name="findWhatLabelFind">
<property name="text">
<string>Find what:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="findWhatComboFind">
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="optionsGroupFind">
<property name="title">
<string>Search options</string>
</property>
<layout class="QVBoxLayout" name="optionsLayoutFind">
<item>
<widget class="QCheckBox" name="matchCaseCheckFind">
<property name="text">
<string>Match case</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="wholeWordCheckFind">
<property name="text">
<string>Match whole word only</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="wrapAroundCheckFind">
<property name="text">
<string>Wrap around</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="inSelectionCheckFind">
<property name="text">
<string>In selection</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="modeLabelFind">
<property name="text">
<string>Search mode:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="modeComboFind"/>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacerFind">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="findButtonsWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="findButtonsLayout">
<item>
<widget class="QPushButton" name="findNextBtnFind">
<property name="text">
<string>Find Next</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="findPrevBtnFind">
<property name="text">
<string>Find Previous</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacerFindButtons">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="closeBtnFind">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="replaceTab">
<attribute name="title">
<string>Replace</string>
</attribute>
<layout class="QHBoxLayout" name="replaceTabLayout">
<item>
<layout class="QVBoxLayout" name="replaceLeftLayout">
<item>
<layout class="QFormLayout" name="replaceFormLayout">
<item row="0" column="0">
<widget class="QLabel" name="findWhatLabelReplace">
<property name="text">
<string>Find what:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="findWhatComboReplace">
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="replaceWithLabelReplace">
<property name="text">
<string>Replace with:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="replaceWithComboReplace">
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="optionsGroupReplace">
<property name="title">
<string>Search options</string>
</property>
<layout class="QVBoxLayout" name="optionsLayoutReplace">
<item>
<widget class="QCheckBox" name="matchCaseCheckReplace">
<property name="text">
<string>Match case</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="wholeWordCheckReplace">
<property name="text">
<string>Match whole word only</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="wrapAroundCheckReplace">
<property name="text">
<string>Wrap around</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="inSelectionCheckReplace">
<property name="text">
<string>In selection</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="modeLabelReplace">
<property name="text">
<string>Search mode:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="modeComboReplace"/>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacerReplace">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="replaceButtonsWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="replaceButtonsLayout">
<item>
<widget class="QPushButton" name="findNextBtnReplace">
<property name="text">
<string>Find Next</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="replaceBtn">
<property name="text">
<string>Replace</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="replaceAllBtn">
<property name="text">
<string>Replace All</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacerReplaceButtons">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="closeBtnReplace">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -5,12 +5,25 @@
#include "documentmanager.h" #include "documentmanager.h"
#include "appsettings.h" #include "appsettings.h"
#include "editorviewhelper.h" #include "editorviewhelper.h"
#include "findreplacedialog.h"
#include <QActionGroup> #include <QActionGroup>
#include <QApplication> #include <QApplication>
#include <QCheckBox>
#include <QCloseEvent>
#include <QComboBox>
#include <QDialog>
#include <QFormLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QMenu> #include <QMenu>
#include <QPushButton>
#include <QStringBuilder> #include <QStringBuilder>
#include <QStatusBar> #include <QStatusBar>
#include <QTabWidget>
#include <QVBoxLayout>
#include <QFileDialog> #include <QFileDialog>
#include <QMessageBox> #include <QMessageBox>
@@ -98,11 +111,18 @@ void MainWindow::setupActions()
}, actionCollection()); }, actionCollection());
// "Search" menu // "Search" menu
KStandardAction::find(this, [](){}, actionCollection()); KStandardAction::find(this, [this](){
KStandardAction::findNext(this, [](){}, actionCollection()); showFindReplaceDialog(false);
KStandardAction::findPrev(this, [](){}, actionCollection()); }, actionCollection());
KStandardAction::replace(this, [](){}, actionCollection()); KStandardAction::findNext(this, [this](){
qDebug() << KStandardAction::name(KStandardAction::Replace); findNextInCurrentEditor(true, true);
}, actionCollection());
KStandardAction::findPrev(this, [this](){
findNextInCurrentEditor(false, true);
}, actionCollection());
KStandardAction::replace(this, [this](){
showFindReplaceDialog(true);
}, actionCollection());
// "Language" menu // "Language" menu
QAction *lexerNoneAction = new QAction(this); QAction *lexerNoneAction = new QAction(this);
@@ -378,3 +398,326 @@ void MainWindow::updateActions()
indentGuideAction->setChecked(m_indentGuidesEnabled); 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<int>(editor->selectionStart());
const int selEnd = static_cast<int>(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<int>(editor->textLength());
int rangeStart = 0;
int rangeEnd = docLen;
if (m_findReplaceDialog->inSelection()) {
if (!m_findInSelectionEnabled) {
const int selStart = static_cast<int>(editor->selectionStart());
const int selEnd = static_cast<int>(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<int>(editor->selectionStart());
const int selEndNow = static_cast<int>(editor->selectionEnd());
int caretBase = static_cast<int>(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<int, int> found = editor->findText(static_cast<int>(flags), needleBytes.constData(), caretBase, rangeEnd);
if (found.first >= 0) {
selectMatch(found.first, found.second);
return true;
}
if (m_findReplaceDialog->wrapAround()) {
const QPair<int, int> wrapped = editor->findText(static_cast<int>(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, int> {
int bestStart = -1;
int bestEnd = -1;
int scanPos = rangeStart;
while (scanPos < endExclusive) {
const QPair<int, int> found = editor->findText(static_cast<int>(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<int, int>(bestStart, bestEnd);
};
QPair<int, int> 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<int>(editor->textLength());
int rangeStart = 0;
int rangeEnd = docLen;
if (m_findReplaceDialog->inSelection()) {
if (!m_findInSelectionEnabled) {
const int selStart = static_cast<int>(editor->selectionStart());
const int selEnd = static_cast<int>(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<int>(editor->targetStart());
const int matchEnd = static_cast<int>(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<int>(replacedLen) - matchLen;
end += delta;
start = matchStart + static_cast<int>(replacedLen);
}
editor->endUndoAction();
if (replacedCount == 0 && showNotFoundMessage) {
QMessageBox::information(this, QStringLiteral("Replace"), QStringLiteral("Text not found."));
}
return replacedCount;
}

View File

@@ -8,6 +8,7 @@ class QLabel;
class SciEdit; class SciEdit;
class TabWidget; class TabWidget;
class DocumentManager; class DocumentManager;
class FindReplaceDialog;
class MainWindow : public KXmlGuiWindow class MainWindow : public KXmlGuiWindow
{ {
@@ -36,6 +37,10 @@ private:
void updateWindowTitle(); void updateWindowTitle();
void updateStatusBar(); void updateStatusBar();
void updateActions(); void updateActions();
void showFindReplaceDialog(bool showReplace);
bool findNextInCurrentEditor(bool forward, bool showNotFoundMessage);
bool replaceOneInCurrentEditor(bool showNotFoundMessage);
int replaceAllInCurrentEditor(bool showNotFoundMessage);
DocumentManager *m_documentManager; DocumentManager *m_documentManager;
TabWidget *m_tabWidget; TabWidget *m_tabWidget;
@@ -43,6 +48,10 @@ private:
QLabel *m_encodingStatusLabel; QLabel *m_encodingStatusLabel;
QLabel *m_languageStatusLabel; QLabel *m_languageStatusLabel;
bool m_indentGuidesEnabled = false; bool m_indentGuidesEnabled = false;
FindReplaceDialog *m_findReplaceDialog = nullptr;
bool m_findInSelectionEnabled = false;
int m_findSelectionStart = 0;
int m_findSelectionEnd = 0;
}; };
// plainly for KConfigDialog // plainly for KConfigDialog