RedPanda-CPP/RedPandaIDE/widgets/searchdialog.cpp

507 lines
18 KiB
C++
Raw Normal View History

2021-12-26 23:18:28 +08:00
/*
* Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
2021-08-02 21:58:39 +08:00
#include "searchdialog.h"
#include "ui_searchdialog.h"
2021-08-02 23:42:10 +08:00
#include <QTabBar>
2021-08-03 23:55:57 +08:00
#include "../editor.h"
#include "../mainwindow.h"
#include "../editorlist.h"
#include <qsynedit/Search.h>
#include <qsynedit/SearchRegex.h>
#include "../project.h"
#include "../settings.h"
2021-08-03 23:55:57 +08:00
#include <QMessageBox>
2021-08-05 12:31:53 +08:00
#include <QDebug>
2021-08-03 23:55:57 +08:00
2021-08-02 21:58:39 +08:00
SearchDialog::SearchDialog(QWidget *parent) :
QDialog(parent),
2021-08-03 23:55:57 +08:00
ui(new Ui::SearchDialog),
mSearchEngine()
2021-08-02 21:58:39 +08:00
{
setWindowFlag(Qt::WindowContextHelpButtonHint,false);
2021-08-02 21:58:39 +08:00
ui->setupUi(this);
2021-08-02 23:42:10 +08:00
mTabBar = new QTabBar();
mTabBar->addTab(tr("Find"));
mTabBar->addTab(tr("Replace"));
mTabBar->addTab(tr("Find in files"));
mTabBar->addTab(tr("Replace in files"));
2021-08-03 23:55:57 +08:00
mTabBar->setExpanding(false);
2021-08-02 23:42:10 +08:00
ui->dialogLayout->insertWidget(0,mTabBar);
connect(mTabBar,&QTabBar::currentChanged,this, &SearchDialog::onTabChanged);
2021-08-03 23:55:57 +08:00
mSearchOptions&=0;
2022-09-27 14:01:38 +08:00
mBasicSearchEngine= QSynedit::PSynSearchBase(new QSynedit::BasicSearcher());
mRegexSearchEngine= QSynedit::PSynSearchBase(new QSynedit::RegexSearcher());
2021-08-02 21:58:39 +08:00
}
SearchDialog::~SearchDialog()
{
delete ui;
}
2021-08-02 23:42:10 +08:00
void SearchDialog::find(const QString &text)
{
2021-08-04 09:13:41 +08:00
if (mTabBar->currentIndex()==0) {
this->onTabChanged();
} else {
mTabBar->setCurrentIndex(0);
}
2021-08-02 23:42:10 +08:00
ui->cbFind->setCurrentText(text);
2022-03-11 20:51:33 +08:00
ui->cbFind->setFocus();
2021-08-02 23:42:10 +08:00
show();
}
void SearchDialog::findNext()
{
if (mTabBar->currentIndex()==0) { // it's a find action
// Disable entire scope searching
ui->rbEntireScope->setChecked(false);
// Always search forwards
ui->rbForward->setChecked(true);
ui->btnExecute->click();
}
}
2021-08-02 23:42:10 +08:00
void SearchDialog::findInFiles(const QString &text)
{
mTabBar->setCurrentIndex(2);
2021-08-02 23:42:10 +08:00
ui->cbFind->setCurrentText(text);
2022-03-11 20:51:33 +08:00
ui->cbFind->setFocus();
2021-08-02 23:42:10 +08:00
show();
}
2022-09-27 14:01:38 +08:00
void SearchDialog::findInFiles(const QString &keyword, SearchFileScope scope, QSynedit::SearchOptions options)
2021-08-05 19:58:32 +08:00
{
mTabBar->setCurrentIndex(2);
2021-08-05 19:58:32 +08:00
ui->cbFind->setCurrentText(keyword);
2022-03-11 20:51:33 +08:00
ui->cbFind->setFocus();
2021-08-05 19:58:32 +08:00
switch(scope) {
case SearchFileScope::currentFile:
ui->rbCurrentFile->setChecked(true);
break;
case SearchFileScope::openedFiles:
ui->rbOpenFiles->setChecked(true);
break;
case SearchFileScope::wholeProject:
ui->rbProject->setChecked(true);
break;
}
// Apply options
2022-09-25 09:55:18 +08:00
ui->chkRegExp->setChecked(options.testFlag(QSynedit::ssoRegExp));
ui->chkCaseSensetive->setChecked(options.testFlag(QSynedit::ssoMatchCase));
ui->chkWholeWord->setChecked(options.testFlag(QSynedit::ssoWholeWord));
ui->chkWrapAround->setChecked(options.testFlag(QSynedit::ssoWholeWord));
2021-08-05 19:58:32 +08:00
show();
}
2021-08-02 23:42:10 +08:00
void SearchDialog::replace(const QString &sFind, const QString &sReplace)
{
mTabBar->setCurrentIndex(1);
2021-08-02 23:42:10 +08:00
ui->cbFind->setCurrentText(sFind);
ui->cbReplace->setCurrentText(sReplace);
2022-03-11 20:51:33 +08:00
ui->cbFind->setFocus();
2021-08-02 23:42:10 +08:00
show();
}
void SearchDialog::onTabChanged()
{
bool isfind = (mTabBar->currentIndex() == 0);
bool isfindfiles = (mTabBar->currentIndex() == 2 || mTabBar->currentIndex() == 3 );
bool isreplace = (mTabBar->currentIndex() == 1);
2021-08-02 23:42:10 +08:00
2021-08-04 09:13:41 +08:00
ui->lblReplace->setVisible(isreplace);
ui->cbReplace->setVisible(isreplace);
2021-08-02 23:42:10 +08:00
ui->grpOrigin->setVisible(isfind || isreplace);
ui->grpOrigin->setEnabled(isfind || isreplace);
2021-08-02 23:42:10 +08:00
ui->grpScope->setVisible(isfind || isreplace);
ui->grpScope->setEnabled(isreplace);
2021-08-04 09:13:41 +08:00
ui->grpWhere->setVisible(isfindfiles);
ui->grpWhere->setEnabled(isfindfiles);
2021-08-02 23:42:10 +08:00
ui->grpDirection->setVisible(isfind || isreplace);
ui->grpDirection->setEnabled(isfind || isreplace);
2021-08-02 23:42:10 +08:00
// grpOption is always visible
// Disable project search option when none is open
// rbProjectFiles.Enabled := Assigned(MainForm.Project);
ui->rbProject->setEnabled(pMainWindow->project()!=nullptr);
ui->rbOpenFiles->setEnabled(pMainWindow->editorList()->pageCount()>0);
2021-08-02 23:42:10 +08:00
// if not Assigned(MainForm.Project) then
// rbOpenFiles.Checked := true;
// Disable prompt when doing finds
2021-08-04 09:13:41 +08:00
ui->chkPrompt->setEnabled(isreplace);
ui->chkPrompt->setVisible(isreplace);
ui->chkWrapAround->setEnabled(!isfindfiles);
ui->chkWrapAround->setVisible(!isfindfiles);
2021-08-02 23:42:10 +08:00
2021-08-04 09:13:41 +08:00
if (isfind || isfindfiles) {
2021-08-02 23:42:10 +08:00
ui->btnExecute->setText(tr("Find"));
} else {
ui->btnExecute->setText(tr("Replace"));
}
setWindowTitle(mTabBar->tabText(mTabBar->currentIndex()));
}
void SearchDialog::on_cbFind_currentTextChanged(const QString &)
{
ui->btnExecute->setEnabled(!ui->cbFind->currentText().isEmpty());
}
void SearchDialog::on_btnCancel_clicked()
{
this->close();
}
static void saveComboHistory(QComboBox* cb,const QString& text) {
QString s = text.trimmed();
if (s.isEmpty())
return;
int i = cb->findText(s);
if (i>=0) {
cb->removeItem(i);
}
cb->insertItem(0,s);
cb->setCurrentText(s);
}
2021-08-03 23:55:57 +08:00
void SearchDialog::on_btnExecute_clicked()
2021-08-02 23:42:10 +08:00
{
2021-08-05 12:31:53 +08:00
int findCount = 0;
saveComboHistory(ui->cbFind,ui->cbFind->currentText());
saveComboHistory(ui->cbReplace,ui->cbReplace->currentText());
2021-08-03 23:55:57 +08:00
SearchAction actionType;
switch (mTabBar->currentIndex()) {
case 0:
actionType = SearchAction::Find;
break;
case 1:
actionType = SearchAction::Replace;
2021-08-03 23:55:57 +08:00
break;
case 2:
actionType = SearchAction::FindFiles;
2021-08-03 23:55:57 +08:00
break;
case 3:
actionType = SearchAction::ReplaceFiles;
break;
default:
return;
}
mSearchOptions&=0;
// Apply options
if (ui->chkRegExp->isChecked()) {
2022-09-25 09:55:18 +08:00
mSearchOptions.setFlag(QSynedit::ssoRegExp);
2021-08-03 23:55:57 +08:00
}
if (ui->chkCaseSensetive->isChecked()) {
2022-09-25 09:55:18 +08:00
mSearchOptions.setFlag(QSynedit::ssoMatchCase);
2021-08-03 23:55:57 +08:00
}
if (ui->chkWholeWord->isChecked()) {
2022-09-25 09:55:18 +08:00
mSearchOptions.setFlag(QSynedit::ssoWholeWord);
2021-08-03 23:55:57 +08:00
}
if (ui->chkWrapAround->isChecked()) {
2022-09-25 09:55:18 +08:00
mSearchOptions.setFlag(QSynedit::ssoWrapAround);
}
2021-08-03 23:55:57 +08:00
// Apply scope, when enabled
if (ui->grpScope->isEnabled()) {
2021-08-03 23:55:57 +08:00
if (ui->rbSelection->isChecked()) {
2022-09-25 09:55:18 +08:00
mSearchOptions.setFlag(QSynedit::ssoSelectedOnly);
2021-08-03 23:55:57 +08:00
}
}
// Apply direction, when enabled
if (ui->grpDirection->isEnabled()) {
2021-08-03 23:55:57 +08:00
if (ui->rbBackward->isChecked()) {
2022-09-25 09:55:18 +08:00
mSearchOptions.setFlag(QSynedit::ssoBackwards);
2021-08-03 23:55:57 +08:00
}
}
// Apply origin, when enabled
if (ui->grpOrigin->isEnabled()) {
2021-08-03 23:55:57 +08:00
if (ui->rbEntireScope->isChecked()) {
2022-09-25 09:55:18 +08:00
mSearchOptions.setFlag(QSynedit::ssoEntireScope);
2021-08-03 23:55:57 +08:00
}
}
// Use entire scope for file finding/replacing
if (actionType == SearchAction::FindFiles || actionType == SearchAction::ReplaceFiles) {
2022-09-25 09:55:18 +08:00
mSearchOptions.setFlag(QSynedit::ssoEntireScope);
2021-08-03 23:55:57 +08:00
}
this->close();
// Find the first one, then quit
if (actionType == SearchAction::Find) {
Editor *e = pMainWindow->editorList()->getEditor();
if (e!=nullptr) {
findCount+=execute(e,ui->cbFind->currentText(),"",nullptr,
[](){
return QMessageBox::question(pMainWindow,
tr("Continue Search"),
tr("End of file has been reached. ")
+tr("Do you want to continue from file's beginning?"),
QMessageBox::Yes|QMessageBox::No,
QMessageBox::Yes) == QMessageBox::Yes;
});
2021-08-03 23:55:57 +08:00
}
2021-08-05 12:31:53 +08:00
} else if (actionType == SearchAction::Replace) {
2021-08-04 09:13:41 +08:00
Editor *e = pMainWindow->editorList()->getEditor();
if (e!=nullptr) {
bool doPrompt = ui->chkPrompt->isChecked();
2021-08-05 12:31:53 +08:00
findCount+=execute(e,ui->cbFind->currentText(),ui->cbReplace->currentText(),
2021-08-04 09:13:41 +08:00
[&doPrompt](const QString& sSearch,
2021-10-20 18:05:43 +08:00
const QString& /*sReplace*/, int /*Line*/, int /*ch*/, int /*wordLen*/){
2021-08-04 09:13:41 +08:00
if (doPrompt) {
switch(QMessageBox::question(pMainWindow,
tr("Replace"),
tr("Replace this occurrence of ''%1''?").arg(sSearch),
QMessageBox::Yes|QMessageBox::YesAll|QMessageBox::No|QMessageBox::Cancel,
QMessageBox::Yes)) {
case QMessageBox::Yes:
2022-09-27 14:01:38 +08:00
return QSynedit::SearchAction::Replace;
2021-08-04 09:13:41 +08:00
case QMessageBox::YesAll:
2022-09-27 14:01:38 +08:00
return QSynedit::SearchAction::ReplaceAll;
2021-08-04 09:13:41 +08:00
case QMessageBox::No:
2022-09-27 14:01:38 +08:00
return QSynedit::SearchAction::Skip;
2021-08-04 09:13:41 +08:00
case QMessageBox::Cancel:
2022-09-27 14:01:38 +08:00
return QSynedit::SearchAction::Exit;
2021-08-04 09:13:41 +08:00
default:
2022-09-27 14:01:38 +08:00
return QSynedit::SearchAction::Exit;
2021-08-04 09:13:41 +08:00
}
} else {
2022-09-27 14:01:38 +08:00
return QSynedit::SearchAction::ReplaceAll;
2021-08-04 09:13:41 +08:00
}
},
[](){
return QMessageBox::question(pMainWindow,
tr("Continue Replace"),
tr("End of file has been reached. ")
+tr("Do you want to continue from file's beginning?"),
QMessageBox::Yes|QMessageBox::No,
QMessageBox::Yes) == QMessageBox::Yes;
2021-08-04 09:13:41 +08:00
});
}
} else if (actionType == SearchAction::FindFiles || actionType == SearchAction::ReplaceFiles) {
2021-08-05 12:31:53 +08:00
int fileSearched = 0;
int fileHitted = 0;
QString keyword = ui->cbFind->currentText();
if (ui->rbOpenFiles->isChecked()) {
PSearchResults results = pMainWindow->searchResultModel()->addSearchResults(
keyword,
mSearchOptions,
SearchFileScope::openedFiles
);
// loop through editors, add results to message control
for (int i=0;i<pMainWindow->editorList()->pageCount();i++) {
Editor * e=pMainWindow->editorList()->operator[](i);
if (e!=nullptr) {
fileSearched++;
PSearchResultTreeItem parentItem = batchFindInEditor(
e,
e->filename(),
keyword);
2021-08-05 12:31:53 +08:00
int t = parentItem->results.size();
findCount+=t;
if (t>0) {
fileHitted++;
results->results.append(parentItem);
}
}
}
pMainWindow->searchResultModel()->notifySearchResultsUpdated();
} else if (ui->rbCurrentFile->isChecked()) {
PSearchResults results = pMainWindow->searchResultModel()->addSearchResults(
keyword,
mSearchOptions,
SearchFileScope::currentFile
2021-08-05 12:31:53 +08:00
);
Editor * e= pMainWindow->editorList()->getEditor();
if (e!=nullptr) {
fileSearched++;
PSearchResultTreeItem parentItem = batchFindInEditor(
e,
e->filename(),
keyword);
2021-08-05 12:31:53 +08:00
int t = parentItem->results.size();
findCount+=t;
if (t>0) {
fileHitted++;
results->results.append(parentItem);
}
}
pMainWindow->searchResultModel()->notifySearchResultsUpdated();
} else if (ui->rbProject->isChecked()) {
PSearchResults results = pMainWindow->searchResultModel()->addSearchResults(
keyword,
mSearchOptions,
SearchFileScope::wholeProject
);
2022-10-01 08:54:44 +08:00
foreach (PProjectUnit unit, pMainWindow->project()->unitList()) {
Editor * e = pMainWindow->project()->unitEditor(unit);
QString curFilename = unit->fileName();
if (e) {
fileSearched++;
PSearchResultTreeItem parentItem = batchFindInEditor(
e,
e->filename(),
keyword);
int t = parentItem->results.size();
findCount+=t;
if (t>0) {
fileHitted++;
results->results.append(parentItem);
}
} else if (fileExists(curFilename)) {
2022-09-25 09:55:18 +08:00
QSynedit::SynEdit editor;
QByteArray realEncoding;
editor.document()->loadFromFile(curFilename,ENCODING_AUTO_DETECT, realEncoding);
fileSearched++;
PSearchResultTreeItem parentItem = batchFindInEditor(
&editor,
curFilename,
keyword);
int t = parentItem->results.size();
findCount+=t;
if (t>0) {
fileHitted++;
results->results.append(parentItem);
}
}
}
pMainWindow->searchResultModel()->notifySearchResultsUpdated();
2021-08-05 12:31:53 +08:00
}
pMainWindow->showSearchPanel(actionType == SearchAction::ReplaceFiles);
2021-08-03 23:55:57 +08:00
}
2021-08-02 23:42:10 +08:00
}
2022-09-25 09:55:18 +08:00
int SearchDialog::execute(QSynedit::SynEdit *editor, const QString &sSearch, const QString &sReplace,
2022-09-27 14:01:38 +08:00
QSynedit::SearchMathedProc matchCallback,
QSynedit::SearchConfirmAroundProc confirmAroundCallback)
2021-08-02 23:42:10 +08:00
{
2021-08-03 23:55:57 +08:00
if (editor==nullptr)
return 0;
// Modify the caret when using 'from cursor' and when the selection is ignored
2022-09-25 09:55:18 +08:00
if (!mSearchOptions.testFlag(QSynedit::ssoEntireScope) && !mSearchOptions.testFlag(QSynedit::ssoSelectedOnly)
2021-08-03 23:55:57 +08:00
&& editor->selAvail()) {
// start at end of selection
2022-09-25 09:55:18 +08:00
if (mSearchOptions.testFlag(QSynedit::ssoBackwards)) {
2021-08-03 23:55:57 +08:00
editor->setCaretXY(editor->blockBegin());
} else {
editor->setCaretXY(editor->blockEnd());
}
}
2022-09-25 09:55:18 +08:00
if (mSearchOptions.testFlag(QSynedit::ssoRegExp)) {
2021-08-03 23:55:57 +08:00
mSearchEngine = mRegexSearchEngine;
} else {
mSearchEngine = mBasicSearchEngine;
}
2021-08-04 09:13:41 +08:00
return editor->searchReplace(sSearch, sReplace, mSearchOptions,
mSearchEngine, matchCallback, confirmAroundCallback);
2021-08-05 12:31:53 +08:00
}
2021-08-03 23:55:57 +08:00
2022-09-25 09:55:18 +08:00
std::shared_ptr<SearchResultTreeItem> SearchDialog::batchFindInEditor(QSynedit::SynEdit *e, const QString& filename,const QString &keyword)
2021-08-05 12:31:53 +08:00
{
//backup
2022-09-25 09:55:18 +08:00
QSynedit::BufferCoord caretBackup = e->caretXY();
QSynedit::BufferCoord blockBeginBackup = e->blockBegin();
QSynedit::BufferCoord blockEndBackup = e->blockEnd();
2021-08-05 12:31:53 +08:00
int toplineBackup = e->topLine();
int leftCharBackup = e->leftChar();
PSearchResultTreeItem parentItem = std::make_shared<SearchResultTreeItem>();
parentItem->filename = filename;
2021-08-05 12:31:53 +08:00
parentItem->parent = nullptr;
execute(e,keyword,"",
[e,&parentItem, filename](const QString&,
2021-08-05 12:31:53 +08:00
const QString&, int Line, int ch, int wordLen){
PSearchResultTreeItem item = std::make_shared<SearchResultTreeItem>();
item->filename = filename;
2021-08-05 12:31:53 +08:00
item->line = Line;
item->start = ch;
item->len = wordLen;
item->parent = parentItem.get();
item->text = e->document()->getString(Line-1);
2021-08-05 19:58:32 +08:00
item->text.replace('\t',' ');
2021-08-05 12:31:53 +08:00
parentItem->results.append(item);
2022-09-27 14:01:38 +08:00
return QSynedit::SearchAction::Skip;
2021-08-05 12:31:53 +08:00
});
// restore
e->setCaretXY(caretBackup);
e->setTopLine(toplineBackup);
e->setLeftChar(leftCharBackup);
e->setCaretAndSelection(
caretBackup,
blockBeginBackup,
blockEndBackup
);
return parentItem;
2021-08-03 23:55:57 +08:00
}
void SearchDialog::showEvent(QShowEvent *event)
{
QDialog::showEvent(event);
if (pSettings->environment().language()=="zh_CN") {
ui->txtRegExpHelp->setText(
QString("<html><head/><body><p><a href=\"%1\"><span style=\" text-decoration: underline; color:#0000ff;\">(?)</span></a></p></body></html>")
.arg("https://www.runoob.com/regexp/regexp-tutorial.html"));
} else {
ui->txtRegExpHelp->setText(
QString("<html><head/><body><p><a href=\"%1\"><span style=\" text-decoration: underline; color:#0000ff;\">(?)</span></a></p></body></html>")
.arg("https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference"));
}
}
QTabBar *SearchDialog::tabBar() const
{
return mTabBar;
}
2022-09-25 09:55:18 +08:00
QSynedit::PSynSearchBase SearchDialog::searchEngine() const
2021-08-03 23:55:57 +08:00
{
return mSearchEngine;
2021-08-02 23:42:10 +08:00
}
void SearchDialog::findPrevious()
{
if (mTabBar->currentIndex()==0) { // it's a find action
// Disable entire scope searching
ui->rbEntireScope->setChecked(false);
// Always search backward
ui->rbBackward->setChecked(true);
ui->btnExecute->click();
}
}