Commit c666e26a authored by Christoph Cullmann's avatar Christoph Cullmann 🐮
Browse files

batch matches together to avoid massive costs for repeated model update

now we update the model in larger chunks making a lot of hits faster
parent 7e6ceed2
......@@ -102,8 +102,6 @@ bool SearchDiskFiles::searching()
void SearchDiskFiles::searchSingleLineRegExp(const QString &fileName)
{
QFile file(fileName);
QUrl fileUrl = QUrl::fromUserInput(fileName);
if (!file.open(QFile::ReadOnly)) {
return;
}
......@@ -113,6 +111,7 @@ void SearchDiskFiles::searchSingleLineRegExp(const QString &fileName)
int i = 0;
int column;
QRegularExpressionMatch match;
QVector<KateSearchMatch> matches;
while (!(line = stream.readLine()).isNull()) {
if (m_cancelSearch)
break;
......@@ -125,7 +124,7 @@ void SearchDiskFiles::searchSingleLineRegExp(const QString &fileName)
if (line.length() > 1024)
line = line.left(1024);
emit matchFound(fileUrl.toString(), fileUrl.fileName(), line, match.capturedLength(), i, column, i, column + match.capturedLength());
matches.push_back(KateSearchMatch{line, match.capturedLength(), i, column, i, column + match.capturedLength()});
match = m_regExp.match(line, column + match.capturedLength());
column = match.capturedStart();
......@@ -137,6 +136,12 @@ void SearchDiskFiles::searchSingleLineRegExp(const QString &fileName)
}
i++;
}
// emit all matches batched
if (!matches.isEmpty()) {
const QUrl fileUrl = QUrl::fromUserInput(fileName);
emit matchesFound(fileUrl.toString(), fileUrl.fileName(), matches);
}
}
void SearchDiskFiles::searchMultiLineRegExp(const QString &fileName)
......@@ -173,6 +178,7 @@ void SearchDiskFiles::searchMultiLineRegExp(const QString &fileName)
QRegularExpressionMatch match;
match = tmpRegExp.match(fullDoc);
column = match.capturedStart();
QVector<KateSearchMatch> matches;
while (column != -1 && !match.captured().isEmpty()) {
if (m_cancelSearch)
break;
......@@ -188,12 +194,11 @@ void SearchDiskFiles::searchMultiLineRegExp(const QString &fileName)
if (line == -1) {
break;
}
QUrl fileUrl = QUrl::fromUserInput(fileName);
int startColumn = (column - lineStart[line]);
int endLine = line + match.captured().count(QLatin1Char('\n'));
int lastNL = match.captured().lastIndexOf(QLatin1Char('\n'));
int endColumn = lastNL == -1 ? startColumn + match.captured().length() : match.captured().length() - lastNL - 1;
emit matchFound(fileUrl.toString(), fileUrl.fileName(), fullDoc.mid(lineStart[line], column - lineStart[line]) + match.captured(), match.capturedLength(), line, startColumn, endLine, endColumn);
matches.push_back(KateSearchMatch{fullDoc.mid(lineStart[line], column - lineStart[line]) + match.captured(), match.capturedLength(), line, startColumn, endLine, endColumn});
match = tmpRegExp.match(fullDoc, column + match.capturedLength());
column = match.capturedStart();
m_matchCount++;
......@@ -202,4 +207,10 @@ void SearchDiskFiles::searchMultiLineRegExp(const QString &fileName)
if (m_matchCount % 50)
msleep(1);
}
// emit all matches batched
if (!matches.isEmpty()) {
const QUrl fileUrl = QUrl::fromUserInput(fileName);
emit matchesFound(fileUrl.toString(), fileUrl.fileName(), matches);
}
}
......@@ -26,6 +26,23 @@
#include <QThread>
#include <QVector>
/**
* data holder for one match in one file
* used to transfer multiple matches at once via signals to avoid heavy costs for files with a lot of matches
*/
class KateSearchMatch
{
public:
QString lineContent;
int matchLen;
int startLine;
int startColumn;
int endLine;
int endColumn;
};
Q_DECLARE_METATYPE(KateSearchMatch)
class SearchDiskFiles : public QThread
{
Q_OBJECT
......@@ -48,7 +65,7 @@ public Q_SLOTS:
void cancelSearch();
Q_SIGNALS:
void matchFound(const QString &url, const QString &docName, const QString &lineContent, int matchLen, int line, int column, int endLine, int endColumn);
void matchesFound(const QString &url, const QString &docName, const QVector<KateSearchMatch> &searchMatches);
void searchDone();
void searching(const QString &file);
......
......@@ -223,6 +223,9 @@ K_PLUGIN_FACTORY_WITH_JSON(KatePluginSearchFactory, "katesearch.json", registerP
KatePluginSearch::KatePluginSearch(QObject *parent, const QList<QVariant> &)
: KTextEditor::Plugin(parent)
{
// ensure we can send over vector of matches via queued connection
qRegisterMetaType<QVector<KateSearchMatch>>();
m_searchCommand = new KateSearchCommand(this);
}
......@@ -451,14 +454,14 @@ KatePluginSearchView::KatePluginSearchView(KTextEditor::Plugin *plugin, KTextEdi
m_ui.displayOptions->setChecked(true);
connect(&m_searchOpenFiles, &SearchOpenFiles::matchFound, this, &KatePluginSearchView::matchFound);
connect(&m_searchOpenFiles, &SearchOpenFiles::matchesFound, this, &KatePluginSearchView::matchesFound);
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::fileListReady, this, &KatePluginSearchView::folderFileListChanged);
connect(&m_folderFilesList, &FolderFilesList::searching, this, &KatePluginSearchView::searching);
connect(&m_searchDiskFiles, &SearchDiskFiles::matchFound, this, &KatePluginSearchView::matchFound);
connect(&m_searchDiskFiles, &SearchDiskFiles::matchesFound, this, &KatePluginSearchView::matchesFound);
connect(&m_searchDiskFiles, &SearchDiskFiles::searchDone, this, &KatePluginSearchView::searchDone);
connect(&m_searchDiskFiles, static_cast<void (SearchDiskFiles::*)(const QString &)>(&SearchDiskFiles::searching), this, &KatePluginSearchView::searching);
......@@ -812,10 +815,14 @@ void KatePluginSearchView::addHeaderItem()
m_curResults->tree->expandItem(item);
}
QTreeWidgetItem *KatePluginSearchView::rootFileItem(const QString &url, const QString &fName)
void KatePluginSearchView::addMatchesToRootFileItem(const QString &url, const QString &fName, const QList<QTreeWidgetItem *> &matchItems)
{
if (!m_curResults) {
return nullptr;
return;
}
if (matchItems.isEmpty()) {
return;
}
// make sure we have a root item
......@@ -826,16 +833,18 @@ QTreeWidgetItem *KatePluginSearchView::rootFileItem(const QString &url, const QS
// return early if search as you type or search in "current file"
if (m_isSearchAsYouType) {
return root;
root->addChildren(matchItems);
return;
} else if (root->childCount() == 1 && m_ui.searchPlaceCombo->currentIndex() == CurrentFile) {
// return early for search in CurrentFile
int matches = root->child(0)->data(0, ReplaceMatches::StartLineRole).toInt() + 1;
int matches = root->child(0)->data(0, ReplaceMatches::StartLineRole).toInt() + matchItems.size();
QString path = root->child(0)->data(0, ReplaceMatches::FileUrlRole).toString();
QString name = root->child(0)->data(0, ReplaceMatches::FileNameRole).toString();
QString tmpUrl = QStringLiteral("%1<b>%2</b>: <b>%3</b>").arg(path, name).arg(matches);
root->child(0)->setData(0, Qt::DisplayRole, tmpUrl);
root->child(0)->setData(0, ReplaceMatches::StartLineRole, matches);
return root->child(0);
root->child(0)->addChildren(matchItems);
return;
}
QUrl fullUrl = QUrl::fromUserInput(url);
......@@ -852,24 +861,25 @@ QTreeWidgetItem *KatePluginSearchView::rootFileItem(const QString &url, const QS
for (int i = 0; i < root->childCount(); i++) {
// qDebug() << root->child(i)->data(0, ReplaceMatches::FileNameRole).toString() << fName;
if ((root->child(i)->data(0, ReplaceMatches::FileUrlRole).toString() == url) && (root->child(i)->data(0, ReplaceMatches::FileNameRole).toString() == fName)) {
int matches = root->child(i)->data(0, ReplaceMatches::StartLineRole).toInt() + 1;
int matches = root->child(i)->data(0, ReplaceMatches::StartLineRole).toInt() + matchItems.size();
QString tmpUrl = QStringLiteral("%1<b>%2</b>: <b>%3</b>").arg(path, name).arg(matches);
root->child(i)->setData(0, Qt::DisplayRole, tmpUrl);
root->child(i)->setData(0, ReplaceMatches::StartLineRole, matches);
return root->child(i);
root->child(i)->addChildren(matchItems);
return;
}
}
// file item not found create a new one
QString tmpUrl = QStringLiteral("%1<b>%2</b>: <b>%3</b>").arg(path, name).arg(1);
QString tmpUrl = QStringLiteral("%1<b>%2</b>: <b>%3</b>").arg(path, name).arg(matchItems.size());
TreeWidgetItem *item = new TreeWidgetItem(root, QStringList(tmpUrl));
item->setData(0, ReplaceMatches::FileUrlRole, url);
item->setData(0, ReplaceMatches::FileNameRole, fName);
item->setData(0, ReplaceMatches::StartLineRole, 1);
item->setData(0, ReplaceMatches::StartLineRole, matchItems.size());
item->setCheckState(0, Qt::Checked);
item->setFlags(item->flags() | Qt::ItemIsAutoTristate);
return item;
item->addChildren(matchItems);
}
void KatePluginSearchView::addMatchMark(KTextEditor::Document *doc, KTextEditor::MovingInterface *miface, QTreeWidgetItem *item)
......@@ -944,54 +954,61 @@ void KatePluginSearchView::addMatchMark(KTextEditor::Document *doc, KTextEditor:
iface->addMark(line, KTextEditor::MarkInterface::markType32);
}
void KatePluginSearchView::matchFound(const QString &url, const QString &fName, const QString &lineContent, int matchLen, int startLine, int startColumn, int endLine, int endColumn)
void KatePluginSearchView::matchesFound(const QString &url, const QString &fName, const QVector<KateSearchMatch> &searchMatches)
{
static constexpr int contextLen = 70;
if (!m_curResults || (sender() == &m_searchDiskFiles && m_blockDiskMatchFound)) {
return;
}
int preLen = contextLen;
int preStart = startColumn - preLen;
if (preStart < 0) {
preLen += preStart;
preStart = 0;
}
QString pre;
if (preLen == contextLen) {
pre = QStringLiteral("...");
}
pre += lineContent.mid(preStart, preLen).toHtmlEscaped();
QString match = lineContent.mid(startColumn, matchLen).toHtmlEscaped();
match.replace(QLatin1Char('\n'), QStringLiteral("\\n"));
QString post = lineContent.mid(startColumn + matchLen, contextLen);
if (post.size() >= contextLen) {
post += QStringLiteral("...");
}
post = post.toHtmlEscaped();
// (line:col)[space][space] ...Line text pre [highlighted match] Line text post....
QString displayText = QStringLiteral("(<b>%1:%2</b>) &nbsp;").arg(startLine + 1).arg(startColumn + 1);
QString matchHighlighted = QStringLiteral("<span style=\"background-color:%1; color:%2;\">%3</span>").arg(m_searchBackgroundColor.color().name()).arg(m_foregroundColor.color().name()).arg(match);
displayText = displayText + pre + matchHighlighted + post;
TreeWidgetItem *item = new TreeWidgetItem(static_cast<TreeWidgetItem*>(nullptr), QStringList{displayText});
item->setData(0, ReplaceMatches::FileUrlRole, url);
item->setData(0, Qt::ToolTipRole, url);
item->setData(0, ReplaceMatches::FileNameRole, fName);
item->setData(0, ReplaceMatches::StartLineRole, startLine);
item->setData(0, ReplaceMatches::StartColumnRole, startColumn);
item->setData(0, ReplaceMatches::MatchLenRole, matchLen);
item->setData(0, ReplaceMatches::PreMatchRole, pre);
item->setData(0, ReplaceMatches::MatchRole, match);
item->setData(0, ReplaceMatches::PostMatchRole, post);
item->setData(0, ReplaceMatches::EndLineRole, endLine);
item->setData(0, ReplaceMatches::EndColumnRole, endColumn);
item->setCheckState(0, Qt::Checked);
rootFileItem(url, fName)->addChild(item);
m_curResults->matches++;
/**
* handle all received matches, add them as one operation to the widget afterwards
*/
QList<QTreeWidgetItem *> items;
for (const auto &searchMatch : searchMatches) {
int preLen = contextLen;
int preStart = searchMatch.startColumn - preLen;
if (preStart < 0) {
preLen += preStart;
preStart = 0;
}
QString pre;
if (preLen == contextLen) {
pre = QStringLiteral("...");
}
pre += searchMatch.lineContent.mid(preStart, preLen).toHtmlEscaped();
QString match = searchMatch.lineContent.mid(searchMatch.startColumn, searchMatch.matchLen).toHtmlEscaped();
match.replace(QLatin1Char('\n'), QStringLiteral("\\n"));
QString post = searchMatch.lineContent.mid(searchMatch.startColumn + searchMatch.matchLen, contextLen);
if (post.size() >= contextLen) {
post += QStringLiteral("...");
}
post = post.toHtmlEscaped();
// (line:col)[space][space] ...Line text pre [highlighted match] Line text post....
QString displayText = QStringLiteral("(<b>%1:%2</b>) &nbsp;").arg(searchMatch.startLine + 1).arg(searchMatch.startColumn + 1);
QString matchHighlighted = QStringLiteral("<span style=\"background-color:%1; color:%2;\">%3</span>").arg(m_searchBackgroundColor.color().name()).arg(m_foregroundColor.color().name()).arg(match);
displayText = displayText + pre + matchHighlighted + post;
TreeWidgetItem *item = new TreeWidgetItem(static_cast<TreeWidgetItem*>(nullptr), QStringList{displayText});
item->setData(0, ReplaceMatches::FileUrlRole, url);
item->setData(0, Qt::ToolTipRole, url);
item->setData(0, ReplaceMatches::FileNameRole, fName);
item->setData(0, ReplaceMatches::StartLineRole, searchMatch.startLine);
item->setData(0, ReplaceMatches::StartColumnRole, searchMatch.startColumn);
item->setData(0, ReplaceMatches::MatchLenRole, searchMatch.matchLen);
item->setData(0, ReplaceMatches::PreMatchRole, pre);
item->setData(0, ReplaceMatches::MatchRole, match);
item->setData(0, ReplaceMatches::PostMatchRole, post);
item->setData(0, ReplaceMatches::EndLineRole, searchMatch.endLine);
item->setData(0, ReplaceMatches::EndColumnRole, searchMatch.endColumn);
item->setCheckState(0, Qt::Checked);
items.push_back(item);
}
addMatchesToRootFileItem(url, fName, items);
m_curResults->matches += items.size();
}
void KatePluginSearchView::clearMarks()
......
......@@ -135,7 +135,7 @@ private Q_SLOTS:
void folderFileListChanged();
void matchFound(const QString &url, const QString &fileName, const QString &lineContent, int matchLen, int startLine, int startColumn, int endLine, int endColumn);
void matchesFound(const QString &url, const QString &fileName, const QVector<KateSearchMatch> &searchMatches);
void addMatchMark(KTextEditor::Document *doc, KTextEditor::MovingInterface *miface, QTreeWidgetItem *item);
......@@ -179,7 +179,7 @@ protected:
void addHeaderItem();
private:
QTreeWidgetItem *rootFileItem(const QString &url, const QString &fName);
void addMatchesToRootFileItem(const QString &url, const QString &fName, const QList<QTreeWidgetItem *> &matchItems);
QStringList filterFiles(const QStringList &files) const;
void updateSearchColors();
......
......@@ -108,21 +108,30 @@ int SearchOpenFiles::searchSingleLineRegExp(KTextEditor::Document *doc, const QR
QElapsedTimer time;
time.start();
int resultLine = 0;
QVector<KateSearchMatch> matches;
for (int line = startLine; line < doc->lines(); line++) {
if (time.elapsed() > 100) {
// qDebug() << "Search time exceeded" << time.elapsed() << line;
return line;
resultLine = line;
break;
}
QRegularExpressionMatch match;
match = regExp.match(doc->line(line));
column = match.capturedStart();
while (column != -1 && !match.captured().isEmpty()) {
emit matchFound(doc->url().toString(), doc->documentName(), doc->line(line), match.capturedLength(), line, column, line, column + match.capturedLength());
matches.push_back(KateSearchMatch{doc->line(line), match.capturedLength(), line, column, line, column + match.capturedLength()});
match = regExp.match(doc->line(line), column + match.capturedLength());
column = match.capturedStart();
}
}
return 0;
// emit all matches batched
if (!matches.isEmpty()) {
emit matchesFound(doc->url().toString(), doc->documentName(), matches);
}
return resultLine;
}
int SearchOpenFiles::searchMultiLineRegExp(KTextEditor::Document *doc, const QRegularExpression &regExp, int inStartLine)
......@@ -165,6 +174,8 @@ int SearchOpenFiles::searchMultiLineRegExp(KTextEditor::Document *doc, const QRe
QRegularExpressionMatch match;
match = tmpRegExp.match(m_fullDoc, column);
column = match.capturedStart();
int resultLine = 0;
QVector<KateSearchMatch> matches;
while (column != -1 && !match.captured().isEmpty()) {
// search for the line number of the match
int i;
......@@ -184,15 +195,22 @@ int SearchOpenFiles::searchMultiLineRegExp(KTextEditor::Document *doc, const QRe
int lastNL = match.captured().lastIndexOf(QLatin1Char('\n'));
int endColumn = lastNL == -1 ? startColumn + match.captured().length() : match.captured().length() - lastNL - 1;
emit matchFound(doc->url().toString(), doc->documentName(), doc->line(startLine).left(column - m_lineStart[startLine]) + match.captured(), match.capturedLength(), startLine, startColumn, endLine, endColumn);
matches.push_back(KateSearchMatch{doc->line(startLine).left(column - m_lineStart[startLine]) + match.captured(), match.capturedLength(), startLine, startColumn, endLine, endColumn});
match = tmpRegExp.match(m_fullDoc, column + match.capturedLength());
column = match.capturedStart();
if (time.elapsed() > 100) {
// qDebug() << "Search time exceeded" << time.elapsed() << line;
return startLine;
resultLine = startLine;
break;
}
}
return 0;
// emit all matches batched
if (!matches.isEmpty()) {
emit matchesFound(doc->url().toString(), doc->documentName(), matches);
}
return resultLine;
}
......@@ -24,6 +24,8 @@
#include <QTimer>
#include <ktexteditor/document.h>
#include <SearchDiskFiles.h>
class SearchOpenFiles : public QObject
{
Q_OBJECT
......@@ -49,7 +51,7 @@ private:
int searchMultiLineRegExp(KTextEditor::Document *doc, const QRegularExpression &regExp, int startLine);
Q_SIGNALS:
void matchFound(const QString &url, const QString &fileName, const QString &lineContent, int matchLen, int line, int column, int endLine, int endColumn);
void matchesFound(const QString &url, const QString &fileName, const QVector<KateSearchMatch> &searchMatches);
void searchDone();
void searching(const QString &file);
......
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