From 61eeef78f91108efb354da898b9ee2b708a607a3 Mon Sep 17 00:00:00 2001 From: Lieven Hey Date: Tue, 17 Sep 2024 11:48:28 +0200 Subject: [PATCH] feat: support regex search Since we use QSortFilterProxyModel we get this for free. This patch adds support for regex search by not escaping the search term if it is prefix with %. For the flamegraph there is a custom implementation, which changes the current QString::contains to a QRegularExpression::match call. Closes: #666 --- src/flamegraph.cpp | 21 ++++++++++++--------- src/resultsbottomuppage.ui | 2 +- src/resultscallercalleepage.ui | 2 +- src/resultstopdownpage.ui | 2 +- src/resultsutil.cpp | 2 +- src/util.cpp | 5 +++++ src/util.h | 3 +++ 7 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/flamegraph.cpp b/src/flamegraph.cpp index 23daae2da..dd56ed300 100644 --- a/src/flamegraph.cpp +++ b/src/flamegraph.cpp @@ -546,14 +546,15 @@ struct SearchResults qint64 directCost = 0; }; -SearchResults applySearch(FrameGraphicsItem* item, const QString& searchValue) +SearchResults applySearch(FrameGraphicsItem* item, const QRegularExpression& expression, const QString& pattern) { SearchResults result; - if (searchValue.isEmpty()) { + if (expression.pattern().isEmpty()) { result.matchType = NoSearch; - } else if (item->symbol().symbol.contains(searchValue, Qt::CaseInsensitive) - || (searchValue == QLatin1String("??") && item->symbol().symbol.isEmpty()) - || item->symbol().binary.contains(searchValue, Qt::CaseInsensitive)) { + } else if (expression.match(item->symbol().symbol).hasMatch() + || item->symbol().symbol.contains(pattern, Qt::CaseInsensitive) + || (pattern == QLatin1String("??") && item->symbol().symbol.isEmpty()) + || item->symbol().binary.contains(pattern, Qt::CaseInsensitive)) { result.directCost += item->cost(); result.matchType = DirectMatch; } @@ -562,7 +563,7 @@ SearchResults applySearch(FrameGraphicsItem* item, const QString& searchValue) const auto children = item->childItems(); for (auto* child : children) { auto* childFrame = static_cast(child); - auto childMatch = applySearch(childFrame, searchValue); + auto childMatch = applySearch(childFrame, expression, pattern); if (result.matchType != DirectMatch && (childMatch.matchType == DirectMatch || childMatch.matchType == ChildMatch)) { result.matchType = ChildMatch; @@ -806,7 +807,8 @@ FlameGraph::FlameGraph(QWidget* parent, Qt::WindowFlags flags) layout->addWidget(searchInput); searchInput->setPlaceholderText(i18n("Search...")); - searchInput->setToolTip(i18n("Search the flame graph for a symbol.")); + searchInput->setToolTip(tr("

Filter the call graph tree.
Prefix with '%' to turn " + "it into an regex.

")); searchInput->setClearButtonEnabled(true); connect(searchInput, &QLineEdit::textChanged, this, &FlameGraph::setSearchValue); connect(this, &FlameGraph::uiResetRequested, this, [this, searchInput] { @@ -1210,9 +1212,10 @@ void FlameGraph::setSearchValue(const QString& value) return; } - m_search = value; + m_search = value.startsWith(QLatin1Char('%')) ? value.mid(1) : value; - auto match = applySearch(m_rootItem, value); + QRegularExpression regex(Util::escapeSearchPatternIfNessessary(value)); + auto match = applySearch(m_rootItem, regex, value); if (value.isEmpty()) { m_searchResultsLabel->hide(); diff --git a/src/resultsbottomuppage.ui b/src/resultsbottomuppage.ui index f5c16c78a..1db3f43d1 100644 --- a/src/resultsbottomuppage.ui +++ b/src/resultsbottomuppage.ui @@ -67,7 +67,7 @@ - Filter the call graph tree. + <html><head/><body><p>Filter the call graph tree.<br/>Prefix with '%' to turn it into an regex.</p></body></html> diff --git a/src/resultscallercalleepage.ui b/src/resultscallercalleepage.ui index f6119633d..4e178e0b3 100644 --- a/src/resultscallercalleepage.ui +++ b/src/resultscallercalleepage.ui @@ -67,7 +67,7 @@ - Filter the call graph tree. + <html><head/><body><p>Filter the call graph tree.<br/>Prefix with '%' to turn it into an regex.</p></body></html> diff --git a/src/resultstopdownpage.ui b/src/resultstopdownpage.ui index 2287335bb..9448a1c35 100644 --- a/src/resultstopdownpage.ui +++ b/src/resultstopdownpage.ui @@ -67,7 +67,7 @@ - Filter the call graph tree. + <html><head/><body><p>Filter the call graph tree.<br/>Prefix with '%' to turn it into an regex.</p></body></html> diff --git a/src/resultsutil.cpp b/src/resultsutil.cpp index 21a4a44fb..d6c0f6c51 100644 --- a/src/resultsutil.cpp +++ b/src/resultsutil.cpp @@ -45,7 +45,7 @@ void connectFilter(QLineEdit* filter, QSortFilterProxyModel* proxy) proxy->setFilterCaseSensitivity(Qt::CaseInsensitive); QObject::connect(timer, &QTimer::timeout, proxy, [filter, proxy]() { - proxy->setFilterRegularExpression(QRegularExpression::escape(filter->text())); + proxy->setFilterRegularExpression(Util::escapeSearchPatternIfNessessary(filter->text())); }); QObject::connect(filter, &QLineEdit::textChanged, timer, [timer]() { timer->start(300); }); } diff --git a/src/util.cpp b/src/util.cpp index 34ab73adc..fc4fe9c72 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -455,3 +455,8 @@ KParts::ReadOnlyPart* Util::createPart(const QString& pluginName) return service->createInstance(); #endif } + +QString Util::escapeSearchPatternIfNessessary(const QString& pattern) +{ + return pattern.startsWith(QLatin1Char('%')) ? pattern.mid(1) : QRegularExpression::escape(pattern); +} diff --git a/src/util.h b/src/util.h index c1ba483b5..451fcf996 100644 --- a/src/util.h +++ b/src/util.h @@ -74,4 +74,7 @@ QString collapseTemplate(const QString& str, int level); QProcessEnvironment appImageEnvironment(); KParts::ReadOnlyPart* createPart(const QString& pluginName); + +// if a pattern is prefixed with % then return the pattern, otherwise do a regex escape +QString escapeSearchPatternIfNessessary(const QString& pattern); }