Commit 69c962f2 authored by Kåre Särs's avatar Kåre Särs

Fix crash if search command is used while searching

Thread safety was only provided by disabling the UI and that breaks
if we use the commands.

Add terminateSearch functions that stop the search without sending
signals and prevent already queued signals from adding matches in the
matches tree.

BUG: 419719
parent 48da8f02
......@@ -45,8 +45,12 @@ void FolderFilesList::run()
QFileInfo folderInfo(m_folder);
checkNextItem(folderInfo);
if (m_cancelSearch)
if (m_cancelSearch) {
m_files.clear();
}
else {
Q_EMIT fileListReady();
}
}
void FolderFilesList::generateList(const QString &folder, bool recursive, bool hidden, bool symlinks, bool binary, const QString &types, const QString &excludes)
......@@ -82,6 +86,12 @@ void FolderFilesList::generateList(const QString &folder, bool recursive, bool h
start();
}
void FolderFilesList::terminateSearch()
{
m_cancelSearch = true;
wait();
}
QStringList FolderFilesList::fileList()
{
return m_files;
......
......@@ -40,6 +40,8 @@ public:
void generateList(const QString &folder, bool recursive, bool hidden, bool symlinks, bool binary, const QString &types, const QString &excludes);
void terminateSearch();
QStringList fileList();
public Q_SLOTS:
......@@ -47,6 +49,7 @@ public Q_SLOTS:
Q_SIGNALS:
void searching(const QString &path);
void fileListReady();
private:
void checkNextItem(const QFileInfo &item);
......
......@@ -42,6 +42,7 @@ void SearchDiskFiles::startSearch(const QStringList &files, const QRegularExpres
return;
}
m_cancelSearch = false;
m_terminateSearch = false;
m_files = files;
m_regExp = regexp;
m_matchCount = 0;
......@@ -67,7 +68,10 @@ void SearchDiskFiles::run()
searchSingleLineRegExp(fileName);
}
}
emit searchDone();
if (!m_terminateSearch) {
emit searchDone();
}
m_cancelSearch = true;
}
......@@ -76,6 +80,13 @@ void SearchDiskFiles::cancelSearch()
m_cancelSearch = true;
}
void SearchDiskFiles::terminateSearch()
{
m_cancelSearch = true;
m_terminateSearch = true;
wait();
}
bool SearchDiskFiles::searching()
{
return !m_cancelSearch;
......@@ -84,6 +95,7 @@ bool SearchDiskFiles::searching()
void SearchDiskFiles::searchSingleLineRegExp(const QString &fileName)
{
QFile file(fileName);
QUrl fileUrl = QUrl::fromUserInput(fileName);
if (!file.open(QFile::ReadOnly)) {
return;
......@@ -100,10 +112,12 @@ void SearchDiskFiles::searchSingleLineRegExp(const QString &fileName)
match = m_regExp.match(line);
column = match.capturedStart();
while (column != -1 && !match.captured().isEmpty()) {
// limit line length
if (m_cancelSearch)
break;
// limit line length in the treeview
if (line.length() > 1024)
line = line.left(1024);
QUrl fileUrl = QUrl::fromUserInput(fileName);
emit matchFound(fileUrl.toString(), fileUrl.fileName(), line, match.capturedLength(), i, column, i, column + match.capturedLength());
match = m_regExp.match(line, column + match.capturedLength());
......
......@@ -39,6 +39,7 @@ public:
void startSearch(const QStringList &iles, const QRegularExpression &regexp);
void run() override;
void terminateSearch();
bool searching();
......@@ -58,6 +59,7 @@ private:
QRegularExpression m_regExp;
QStringList m_files;
bool m_cancelSearch = true;
bool m_terminateSearch = false;
int m_matchCount = 0;
QElapsedTimer m_statusTime;
};
......
......@@ -314,14 +314,6 @@ void KatePluginSearchView::nextFocus(QWidget *currentWidget, bool *found, bool n
KatePluginSearchView::KatePluginSearchView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mainWin, KTextEditor::Application *application)
: QObject(mainWin)
, m_kateApp(application)
, m_curResults(nullptr)
, m_searchJustOpened(false)
, m_projectSearchPlaceIndex(0)
, m_searchDiskFilesDone(true)
, m_searchOpenFilesDone(true)
, m_isSearchAsYouType(false)
, m_isLeftRight(false)
, m_projectPluginView(nullptr)
, m_mainWindow(mainWin)
{
KXMLGUIClient::setComponentName(QStringLiteral("katesearch"), i18n("Kate Search & Replace"));
......@@ -426,10 +418,7 @@ KatePluginSearchView::KatePluginSearchView(KTextEditor::Plugin *plugin, KTextEdi
}
});
connect(m_ui.stopButton, &QPushButton::clicked, &m_searchOpenFiles, &SearchOpenFiles::cancelSearch);
connect(m_ui.stopButton, &QPushButton::clicked, &m_searchDiskFiles, &SearchDiskFiles::cancelSearch);
connect(m_ui.stopButton, &QPushButton::clicked, &m_folderFilesList, &FolderFilesList::cancelSearch);
connect(m_ui.stopButton, &QPushButton::clicked, &m_replacer, &ReplaceMatches::cancelReplace);
connect(m_ui.stopButton, &QPushButton::clicked, this, &KatePluginSearchView::stopClicked);
connect(m_ui.nextButton, &QToolButton::clicked, this, &KatePluginSearchView::goToNextMatch);
......@@ -443,7 +432,7 @@ KatePluginSearchView::KatePluginSearchView(KTextEditor::Plugin *plugin, KTextEdi
connect(&m_searchOpenFiles, &SearchOpenFiles::searchDone, this, &KatePluginSearchView::searchDone);
connect(&m_searchOpenFiles, static_cast<void (SearchOpenFiles::*)(const QString &)>(&SearchOpenFiles::searching), this, &KatePluginSearchView::searching);
connect(&m_folderFilesList, &FolderFilesList::finished, this, &KatePluginSearchView::folderFileListChanged);
connect(&m_folderFilesList, &FolderFilesList::fileListReady, this, &KatePluginSearchView::folderFileListChanged);
connect(&m_folderFilesList, &FolderFilesList::searching, this, &KatePluginSearchView::searching);
connect(&m_searchDiskFiles, &SearchDiskFiles::matchFound, this, &KatePluginSearchView::matchFound);
......@@ -938,7 +927,7 @@ static const int contextLen = 70;
void KatePluginSearchView::matchFound(const QString &url, const QString &fName, const QString &lineContent, int matchLen, int startLine, int startColumn, int endLine, int endColumn)
{
if (!m_curResults) {
if (!m_curResults || (sender() == &m_searchDiskFiles && m_blockDiskMatchFound)) {
return;
}
int preLen = contextLen;
......@@ -1021,8 +1010,31 @@ void KatePluginSearchView::clearDocMarks(KTextEditor::Document *doc)
}
}
void KatePluginSearchView::stopClicked()
{
m_folderFilesList.cancelSearch();
m_searchOpenFiles.cancelSearch();
m_searchDiskFiles.cancelSearch();
m_replacer.cancelReplace();
m_searchDiskFilesDone = true;
m_searchOpenFilesDone = true;
searchDone(); // Just in case the folder list was being populated...
}
void KatePluginSearchView::startSearch()
{
// Forcefully stop any ongoing search or replace
m_blockDiskMatchFound = true; // Do not allow leftover machFound:s from a previous search to be added
m_folderFilesList.terminateSearch();
m_searchOpenFiles.terminateSearch();
m_searchDiskFiles.terminateSearch();
// Re-enable the handling of fisk-file-matches after one event loop
// For some reason blocking of signals or disconnect/connect does not prevent the slot from being called,
// so we use m_blockDiskMatchFound to skip any old matchFound signals during the first event loop.
// New matches from disk-files should not come before the first event loop has executed.
QTimer::singleShot(0, this, [this]() { m_blockDiskMatchFound = false; });
m_replacer.terminateReplace();
m_changeTimer.stop(); // make sure not to start a "while you type" search now
m_mainWindow->showToolView(m_toolView); // in case we are invoked from the command interface
m_projectSearchPlaceIndex = 0; // now that we started, don't switch back automatically
......@@ -2053,6 +2065,7 @@ void KatePluginSearchView::tabCloseRequested(int index)
if (m_curResults == tmp) {
m_searchOpenFiles.cancelSearch();
m_searchDiskFiles.cancelSearch();
m_folderFilesList.cancelSearch();
}
if (m_ui.resultTabWidget->count() > 1) {
delete tmp; // remove the tab
......
......@@ -108,6 +108,7 @@ public:
void writeSessionConfig(KConfigGroup &config) override;
public Q_SLOTS:
void stopClicked();
void startSearch();
void setSearchString(const QString &pattern);
void navigateFolderUp();
......@@ -188,13 +189,14 @@ private:
ReplaceMatches m_replacer;
QAction *m_matchCase = nullptr;
QAction *m_useRegExp = nullptr;
Results *m_curResults;
bool m_searchJustOpened;
int m_projectSearchPlaceIndex;
bool m_searchDiskFilesDone;
bool m_searchOpenFilesDone;
bool m_isSearchAsYouType;
bool m_isLeftRight;
Results *m_curResults = nullptr;
bool m_searchJustOpened = false;
int m_projectSearchPlaceIndex = 0;
bool m_searchDiskFilesDone = true;
bool m_searchOpenFilesDone = true;
bool m_isSearchAsYouType = false;
bool m_isLeftRight = false;
bool m_blockDiskMatchFound = false;
QString m_resultBaseDir;
QList<KTextEditor::MovingRange *> m_matchRanges;
QTimer m_changeTimer;
......@@ -204,7 +206,7 @@ private:
/**
* current project plugin view, if any
*/
QObject *m_projectPluginView;
QObject *m_projectPluginView = nullptr;
/**
* our main window
......
......@@ -42,6 +42,7 @@ void ReplaceMatches::replaceChecked(QTreeWidget *tree, const QRegularExpression
m_regExp = regexp;
m_replaceText = replace;
m_cancelReplace = false;
m_terminateReplace = false;
m_progressTime.restart();
doReplaceNextMatch();
}
......@@ -56,6 +57,12 @@ void ReplaceMatches::cancelReplace()
m_cancelReplace = true;
}
void ReplaceMatches::terminateReplace()
{
m_cancelReplace = true;
m_terminateReplace = true;
}
KTextEditor::Document *ReplaceMatches::findNamed(const QString &name)
{
const QList<KTextEditor::Document *> docs = m_manager->documents();
......@@ -201,6 +208,10 @@ bool ReplaceMatches::replaceSingleMatch(KTextEditor::Document *doc, QTreeWidgetI
void ReplaceMatches::doReplaceNextMatch()
{
if (m_terminateReplace) {
return;
}
if (!m_manager || m_tree->topLevelItemCount() != 1) {
updateTreeViewItems(nullptr);
m_rootIndex = -1;
......
......@@ -61,6 +61,7 @@ public:
public Q_SLOTS:
void cancelReplace();
void terminateReplace();
private Q_SLOTS:
void doReplaceNextMatch();
......@@ -82,6 +83,7 @@ private:
QRegularExpression m_regExp;
QString m_replaceText;
bool m_cancelReplace = false;
bool m_terminateReplace = false;
QElapsedTimer m_progressTime;
};
......
......@@ -25,7 +25,9 @@
SearchOpenFiles::SearchOpenFiles(QObject *parent)
: QObject(parent)
{
connect(this, &SearchOpenFiles::searchNextFile, this, &SearchOpenFiles::doSearchNextFile, Qt::QueuedConnection);
m_nextRunTimer.setInterval(0);
m_nextRunTimer.setSingleShot(true);
connect(&m_nextRunTimer, &QTimer::timeout, this, [this](){ doSearchNextFile(m_nextLine); });
}
bool SearchOpenFiles::searching()
......@@ -35,15 +37,26 @@ bool SearchOpenFiles::searching()
void SearchOpenFiles::startSearch(const QList<KTextEditor::Document *> &list, const QRegularExpression &regexp)
{
if (m_nextIndex != -1)
if (m_nextFileIndex != -1)
return;
m_docList = list;
m_nextIndex = 0;
m_nextFileIndex = 0;
m_regExp = regexp;
m_cancelSearch = false;
m_terminateSearch = false;
m_statusTime.restart();
emit searchNextFile(0);
m_nextLine = 0;
m_nextRunTimer.start(0);
}
void SearchOpenFiles::terminateSearch()
{
m_cancelSearch = true;
m_terminateSearch = true;
m_nextFileIndex = -1;
m_nextLine = -1;
m_nextRunTimer.stop();
}
void SearchOpenFiles::cancelSearch()
......@@ -53,29 +66,30 @@ void SearchOpenFiles::cancelSearch()
void SearchOpenFiles::doSearchNextFile(int startLine)
{
if (m_cancelSearch || m_nextIndex >= m_docList.size()) {
m_nextIndex = -1;
if (m_cancelSearch || m_nextFileIndex >= m_docList.size()) {
m_nextFileIndex = -1;
m_cancelSearch = true;
emit searchDone();
m_nextLine = -1;
return;
}
// NOTE The document managers signal documentWillBeDeleted() must be connected to
// cancelSearch(). A closed file could lead to a crash if it is not handled.
int line = searchOpenFile(m_docList[m_nextIndex], m_regExp, startLine);
int line = searchOpenFile(m_docList[m_nextFileIndex], m_regExp, startLine);
if (line == 0) {
// file searched go to next
m_nextIndex++;
if (m_nextIndex == m_docList.size()) {
m_nextIndex = -1;
m_nextFileIndex++;
if (m_nextFileIndex == m_docList.size()) {
m_nextFileIndex = -1;
m_cancelSearch = true;
emit searchDone();
} else {
emit searchNextFile(0);
m_nextLine = 0;
}
} else {
emit searchNextFile(line);
m_nextLine = line;
}
m_nextRunTimer.start();
}
int SearchOpenFiles::searchOpenFile(KTextEditor::Document *doc, const QRegularExpression &regExp, int startLine)
......
......@@ -25,6 +25,7 @@
#include <QObject>
#include <QRegularExpression>
#include <ktexteditor/document.h>
#include <QTimer>
class SearchOpenFiles : public QObject
{
......@@ -35,6 +36,7 @@ public:
void startSearch(const QList<KTextEditor::Document *> &list, const QRegularExpression &regexp);
bool searching();
void terminateSearch();
public Q_SLOTS:
void cancelSearch();
......@@ -50,16 +52,18 @@ private:
int searchMultiLineRegExp(KTextEditor::Document *doc, const QRegularExpression &regExp, int startLine);
Q_SIGNALS:
void searchNextFile(int startLine);
void matchFound(const QString &url, const QString &fileName, const QString &lineContent, int matchLen, int line, int column, int endLine, int endColumn);
void matchFound(const QString &url, const QString &fileName, const QString &lineContent, int matchLen, int line, int column, int endLine, int endColumn);
void searchDone();
void searching(const QString &file);
private:
QList<KTextEditor::Document *> m_docList;
int m_nextIndex = -1;
int m_nextFileIndex = -1;
QTimer m_nextRunTimer;
int m_nextLine = -1;
QRegularExpression m_regExp;
bool m_cancelSearch = true;
bool m_terminateSearch = false;
QString m_fullDoc;
QVector<int> m_lineStart;
QElapsedTimer m_statusTime;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment