452 lines
16 KiB
C++
452 lines
16 KiB
C++
/*
|
|
* 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/>.
|
|
*/
|
|
#include "searchinfiledialog.h"
|
|
#include "ui_searchinfiledialog.h"
|
|
#include <QTabBar>
|
|
#include "../editor.h"
|
|
#include "../mainwindow.h"
|
|
#include "../editorlist.h"
|
|
#include <qsynedit/searcher/basicsearcher.h>
|
|
#include <qsynedit/searcher/regexsearcher.h>
|
|
#include "../project.h"
|
|
#include "../settings.h"
|
|
#include "../systemconsts.h"
|
|
#include <QMessageBox>
|
|
#include <QDebug>
|
|
#include <QProgressDialog>
|
|
#include <QCompleter>
|
|
#include <QStack>
|
|
#include <QFileDialog>
|
|
|
|
|
|
SearchInFileDialog::SearchInFileDialog(QWidget *parent) :
|
|
QDialog(parent),
|
|
ui(new Ui::SearchInFileDialog)
|
|
{
|
|
setWindowFlag(Qt::WindowContextHelpButtonHint,false);
|
|
ui->setupUi(this);
|
|
mSearchOptions&=0;
|
|
mBasicSearchEngine= QSynedit::PSynSearchBase(new QSynedit::BasicSearcher());
|
|
mRegexSearchEngine= QSynedit::PSynSearchBase(new QSynedit::RegexSearcher());
|
|
ui->cbFind->completer()->setCaseSensitivity(Qt::CaseSensitive);
|
|
on_rbFolder_toggled(false);
|
|
}
|
|
|
|
SearchInFileDialog::~SearchInFileDialog()
|
|
{
|
|
delete ui;
|
|
}
|
|
|
|
|
|
void SearchInFileDialog::findInFiles(const QString &text)
|
|
{
|
|
ui->cbFind->setCurrentText(text);
|
|
ui->cbFind->setFocus();
|
|
show();
|
|
}
|
|
|
|
void SearchInFileDialog::findInFiles(const QString &keyword, SearchFileScope scope, QSynedit::SearchOptions options, const QString& folder, const QString& filters, bool searchSubfolders)
|
|
{
|
|
ui->cbFind->setCurrentText(keyword);
|
|
ui->cbFind->setFocus();
|
|
|
|
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;
|
|
case SearchFileScope::Folder:
|
|
ui->txtFolder->setText(folder);
|
|
ui->txtFilters->setText(filters);
|
|
ui->cbSearchSubFolders->setChecked(searchSubfolders);
|
|
ui->rbFolder->setChecked(true);
|
|
break;
|
|
}
|
|
// Apply options
|
|
ui->chkRegExp->setChecked(options.testFlag(QSynedit::ssoRegExp));
|
|
ui->chkCaseSensetive->setChecked(options.testFlag(QSynedit::ssoMatchCase));
|
|
ui->chkWholeWord->setChecked(options.testFlag(QSynedit::ssoWholeWord));
|
|
show();
|
|
}
|
|
|
|
void SearchInFileDialog::on_cbFind_currentTextChanged(const QString &value)
|
|
{
|
|
ui->btnExecute->setEnabled(!value.isEmpty());
|
|
ui->btnReplace->setEnabled(!value.isEmpty());
|
|
}
|
|
|
|
void SearchInFileDialog::on_btnCancel_clicked()
|
|
{
|
|
this->close();
|
|
}
|
|
|
|
void SearchInFileDialog::on_btnExecute_clicked()
|
|
{
|
|
doSearch(false);
|
|
}
|
|
|
|
void SearchInFileDialog::doSearch(bool replace)
|
|
{
|
|
saveComboHistory(ui->cbFind,ui->cbFind->currentText());
|
|
|
|
mSearchOptions&=0;
|
|
|
|
// Apply options
|
|
if (ui->chkRegExp->isChecked()) {
|
|
mSearchOptions.setFlag(QSynedit::ssoRegExp);
|
|
}
|
|
if (ui->chkCaseSensetive->isChecked()) {
|
|
mSearchOptions.setFlag(QSynedit::ssoMatchCase);
|
|
}
|
|
if (ui->chkWholeWord->isChecked()) {
|
|
mSearchOptions.setFlag(QSynedit::ssoWholeWord);
|
|
}
|
|
|
|
mSearchOptions.setFlag(QSynedit::ssoEntireScope);
|
|
|
|
close();
|
|
|
|
int findCount=0;
|
|
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);
|
|
int t = parentItem->results.size();
|
|
findCount+=t;
|
|
if (t>0) {
|
|
fileHitted++;
|
|
results->results.append(parentItem);
|
|
}
|
|
}
|
|
}
|
|
pMainWindow->searchResultModel()->notifySearchResultsUpdated();
|
|
} else if (ui->rbFolder->isChecked()) {
|
|
PSearchResults results = pMainWindow->searchResultModel()->addSearchResults(
|
|
keyword,
|
|
mSearchOptions,
|
|
SearchFileScope::Folder,
|
|
ui->txtFolder->text(),
|
|
ui->txtFilters->text(),
|
|
ui->cbSearchSubFolders
|
|
);
|
|
if (ui->txtFilters->text().trimmed().isEmpty()) {
|
|
ui->txtFilters->setText("*.*");
|
|
}
|
|
QDir rootDir(ui->txtFolder->text());
|
|
// loop through editors, add results to message control
|
|
QProgressDialog progressDlg(
|
|
tr("Searching..."),
|
|
tr("Abort"),
|
|
0,
|
|
1,
|
|
pMainWindow);
|
|
|
|
progressDlg.setWindowModality(Qt::WindowModal);
|
|
QStack<QDir> dirs;
|
|
QSet<QString> searched;
|
|
QList<QFileInfo> files;
|
|
dirs.push(rootDir);
|
|
QDir::Filters filterOptions=QDir::Files | QDir::NoSymLinks;
|
|
if (PATH_SENSITIVITY==Qt::CaseSensitive)
|
|
filterOptions |= QDir::CaseSensitive;
|
|
while (!dirs.isEmpty()) {
|
|
QDir dir=dirs.back();
|
|
dirs.pop_back();
|
|
foreach(const QFileInfo& entry, dir.entryInfoList(QDir::NoSymLinks | QDir::Dirs)) {
|
|
if (entry.fileName()==".." || entry.fileName()==".")
|
|
continue;
|
|
if (!searched.contains(entry.absoluteFilePath())) {
|
|
dirs.push_back(QDir(entry.absoluteFilePath()));
|
|
searched.insert(entry.absoluteFilePath());
|
|
}
|
|
}
|
|
foreach(const QFileInfo& entry, dir.entryInfoList(ui->txtFilters->text().split(";"), filterOptions)) {
|
|
files.append(entry);
|
|
}
|
|
}
|
|
progressDlg.setMaximum(files.count());
|
|
int i=0;
|
|
foreach (const QFileInfo &info, files) {
|
|
QString curFilename = info.absoluteFilePath();
|
|
i++;
|
|
progressDlg.setValue(i);
|
|
progressDlg.setLabelText(tr("Searching...")+"<br/>"+curFilename);
|
|
|
|
if (progressDlg.wasCanceled())
|
|
break;
|
|
Editor * e = pMainWindow->editorList()->getOpenedEditorByFilename(curFilename);
|
|
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)) {
|
|
QSynedit::QSynEdit editor;
|
|
QByteArray realEncoding;
|
|
try{
|
|
editor.document()->loadFromFile(curFilename,ENCODING_AUTO_DETECT, realEncoding);
|
|
} catch (QSynedit::BinaryFileError e) {
|
|
continue;
|
|
} catch (FileError e) {
|
|
continue;
|
|
}
|
|
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();
|
|
} else if (ui->rbCurrentFile->isChecked()) {
|
|
PSearchResults results = pMainWindow->searchResultModel()->addSearchResults(
|
|
keyword,
|
|
mSearchOptions,
|
|
SearchFileScope::currentFile
|
|
);
|
|
Editor * e= pMainWindow->editorList()->getEditor();
|
|
if (e!=nullptr) {
|
|
fileSearched++;
|
|
PSearchResultTreeItem parentItem = batchFindInEditor(
|
|
e,
|
|
e->filename(),
|
|
keyword);
|
|
int t = parentItem->results.size();
|
|
findCount+=t;
|
|
if (t>0) {
|
|
fileHitted++;
|
|
results->results.append(parentItem);
|
|
}
|
|
}
|
|
pMainWindow->searchResultModel()->notifySearchResultsUpdated();
|
|
} else if (ui->rbProject->isChecked()) {
|
|
if (!pMainWindow->project())
|
|
return;
|
|
PSearchResults results = pMainWindow->searchResultModel()->addSearchResults(
|
|
keyword,
|
|
mSearchOptions,
|
|
SearchFileScope::wholeProject
|
|
);
|
|
QByteArray projectEncoding = pMainWindow->project()->options().encoding;
|
|
QProgressDialog progressDlg(
|
|
tr("Searching..."),
|
|
tr("Abort"),
|
|
0,
|
|
pMainWindow->project()->unitList().count(),
|
|
pMainWindow);
|
|
|
|
progressDlg.setWindowModality(Qt::WindowModal);
|
|
int i=0;
|
|
foreach (PProjectUnit unit, pMainWindow->project()->unitList()) {
|
|
i++;
|
|
progressDlg.setValue(i);
|
|
progressDlg.setLabelText(tr("Searching...")+"<br/>"+unit->fileName());
|
|
|
|
if (progressDlg.wasCanceled())
|
|
break;
|
|
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)) {
|
|
QSynedit::QSynEdit editor;
|
|
QByteArray encoding=unit->encoding();
|
|
if (encoding==ENCODING_PROJECT)
|
|
encoding = projectEncoding;
|
|
QByteArray realEncoding;
|
|
try {
|
|
editor.document()->loadFromFile(curFilename,encoding, realEncoding);
|
|
} catch (QSynedit::BinaryFileError e) {
|
|
continue;
|
|
} catch (FileError e) {
|
|
continue;
|
|
}
|
|
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();
|
|
}
|
|
pMainWindow->showSearchPanel(replace);
|
|
|
|
}
|
|
|
|
int SearchInFileDialog::execute(QSynedit::QSynEdit *editor, const QString &sSearch, const QString &sReplace,
|
|
QSynedit::SearchMathedProc matchCallback,
|
|
QSynedit::SearchConfirmAroundProc confirmAroundCallback)
|
|
{
|
|
if (editor==nullptr)
|
|
return 0;
|
|
// Modify the caret when using 'from cursor' and when the selection is ignored
|
|
if (!mSearchOptions.testFlag(QSynedit::ssoEntireScope) && !mSearchOptions.testFlag(QSynedit::ssoSelectedOnly)
|
|
&& editor->selAvail()) {
|
|
// start at end of selection
|
|
if (mSearchOptions.testFlag(QSynedit::ssoBackwards)) {
|
|
editor->setCaretXY(editor->blockBegin());
|
|
} else {
|
|
editor->setCaretXY(editor->blockEnd());
|
|
}
|
|
}
|
|
|
|
QSynedit::PSynSearchBase searchEngine;
|
|
if (mSearchOptions.testFlag(QSynedit::ssoRegExp)) {
|
|
searchEngine = mRegexSearchEngine;
|
|
} else {
|
|
searchEngine = mBasicSearchEngine;
|
|
}
|
|
|
|
return editor->searchReplace(sSearch, sReplace, mSearchOptions,
|
|
searchEngine, matchCallback, confirmAroundCallback);
|
|
}
|
|
|
|
std::shared_ptr<SearchResultTreeItem> SearchInFileDialog::batchFindInEditor(QSynedit::QSynEdit *e, const QString& filename,const QString &keyword)
|
|
{
|
|
//backup
|
|
QSynedit::BufferCoord caretBackup = e->caretXY();
|
|
QSynedit::BufferCoord blockBeginBackup = e->blockBegin();
|
|
QSynedit::BufferCoord blockEndBackup = e->blockEnd();
|
|
int toplineBackup = e->topLine();
|
|
int leftCharBackup = e->leftChar();
|
|
|
|
PSearchResultTreeItem parentItem = std::make_shared<SearchResultTreeItem>();
|
|
parentItem->filename = filename;
|
|
parentItem->parent = nullptr;
|
|
execute(e,keyword,"",
|
|
[e,&parentItem, filename](const QString&,
|
|
const QString&, int Line, int ch, int wordLen){
|
|
PSearchResultTreeItem item = std::make_shared<SearchResultTreeItem>();
|
|
item->filename = filename;
|
|
item->line = Line;
|
|
item->start = ch;
|
|
item->len = wordLen;
|
|
item->parent = parentItem.get();
|
|
item->text = e->document()->getLine(Line-1);
|
|
item->text.replace('\t',' ');
|
|
parentItem->results.append(item);
|
|
return QSynedit::SearchAction::Skip;
|
|
});
|
|
|
|
// restore
|
|
e->setCaretXY(caretBackup);
|
|
e->setTopLine(toplineBackup);
|
|
e->setLeftChar(leftCharBackup);
|
|
e->setCaretAndSelection(
|
|
caretBackup,
|
|
blockBeginBackup,
|
|
blockEndBackup
|
|
);
|
|
return parentItem;
|
|
}
|
|
|
|
void SearchInFileDialog::showEvent(QShowEvent *event)
|
|
{
|
|
QDialog::showEvent(event);
|
|
ui->rbProject->setEnabled(pMainWindow->project()!=nullptr);
|
|
if (ui->rbProject->isChecked() && !ui->rbProject->isEnabled())
|
|
ui->rbCurrentFile->setChecked(true);
|
|
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"));
|
|
}
|
|
}
|
|
|
|
void SearchInFileDialog::on_btnReplace_clicked()
|
|
{
|
|
doSearch(true);
|
|
}
|
|
|
|
|
|
void SearchInFileDialog::on_rbFolder_toggled(bool checked)
|
|
{
|
|
// ui->lblFilters->setVisible(checked);
|
|
ui->txtFilters->setEnabled(checked);
|
|
// ui->lblFolder->setVisible(checked);
|
|
ui->txtFolder->setEnabled(checked);
|
|
ui->btnChangeFolder->setEnabled(checked);
|
|
ui->cbSearchSubFolders->setEnabled(checked);
|
|
if (checked) {
|
|
if (!directoryExists(ui->txtFolder->text()))
|
|
ui->txtFolder->setText(pSettings->environment().currentFolder());
|
|
}
|
|
}
|
|
|
|
|
|
void SearchInFileDialog::on_btnChangeFolder_clicked()
|
|
{
|
|
QString folder = QFileDialog::getExistingDirectory(this,tr("Choose Folder"),
|
|
ui->txtFolder->text());
|
|
if (directoryExists(folder))
|
|
ui->txtFolder->setText(folder);
|
|
}
|
|
|