Skip to content

Commit 593437c

Browse files
committed
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
1 parent 36bedef commit 593437c

14 files changed

+124
-43
lines changed

src/flamegraph.cpp

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -546,14 +546,13 @@ struct SearchResults
546546
qint64 directCost = 0;
547547
};
548548

549-
SearchResults applySearch(FrameGraphicsItem* item, const QString& searchValue)
549+
SearchResults applySearch(FrameGraphicsItem* item, const QRegularExpression& expression)
550550
{
551551
SearchResults result;
552-
if (searchValue.isEmpty()) {
552+
if (expression.pattern().isEmpty()) {
553553
result.matchType = NoSearch;
554-
} else if (item->symbol().symbol.contains(searchValue, Qt::CaseInsensitive)
555-
|| (searchValue == QLatin1String("??") && item->symbol().symbol.isEmpty())
556-
|| item->symbol().binary.contains(searchValue, Qt::CaseInsensitive)) {
554+
} else if (expression.match(item->symbol().symbol).hasMatch() || expression.match(item->symbol().binary).hasMatch()
555+
|| (expression.pattern() == QLatin1String("\\?\\?") && item->symbol().symbol.isEmpty())) {
557556
result.directCost += item->cost();
558557
result.matchType = DirectMatch;
559558
}
@@ -562,7 +561,7 @@ SearchResults applySearch(FrameGraphicsItem* item, const QString& searchValue)
562561
const auto children = item->childItems();
563562
for (auto* child : children) {
564563
auto* childFrame = static_cast<FrameGraphicsItem*>(child);
565-
auto childMatch = applySearch(childFrame, searchValue);
564+
auto childMatch = applySearch(childFrame, expression);
566565
if (result.matchType != DirectMatch
567566
&& (childMatch.matchType == DirectMatch || childMatch.matchType == ChildMatch)) {
568567
result.matchType = ChildMatch;
@@ -805,13 +804,26 @@ FlameGraph::FlameGraph(QWidget* parent, Qt::WindowFlags flags)
805804
searchInput->setMinimumWidth(200);
806805
layout->addWidget(searchInput);
807806

807+
auto regexCheckBox = new QCheckBox(widget);
808+
regexCheckBox->setText(tr("Regex Search"));
809+
layout->addWidget(regexCheckBox);
810+
808811
searchInput->setPlaceholderText(i18n("Search..."));
809812
searchInput->setToolTip(i18n("<qt>Search the flame graph for a symbol.</qt>"));
810813
searchInput->setClearButtonEnabled(true);
811-
connect(searchInput, &QLineEdit::textChanged, this, &FlameGraph::setSearchValue);
812-
connect(this, &FlameGraph::uiResetRequested, this, [this, searchInput] {
814+
connect(searchInput, &QLineEdit::textChanged, this,
815+
[this](const QString& value) { this->setSearchValue(value, m_useRegex); });
816+
auto applyRegexCheckBox = [this](bool checked) { this->setSearchValue(m_search, checked); };
817+
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
818+
connect(regexCheckBox, &QCheckBox::stateChanged, this, applyRegexCheckBox);
819+
#else
820+
connect(regexCheckBox, &QCheckBox::checkStateChanged, this, applyRegexCheckBox);
821+
#endif
822+
connect(this, &FlameGraph::uiResetRequested, this, [this, searchInput, regexCheckBox] {
813823
m_search.clear();
824+
m_useRegex = false;
814825
searchInput->clear();
826+
regexCheckBox->setChecked(false);
815827
});
816828
},
817829
this);
@@ -1140,7 +1152,7 @@ void FlameGraph::setData(FrameGraphicsItem* rootItem)
11401152
m_scene->addItem(rootItem);
11411153

11421154
if (!m_search.isEmpty()) {
1143-
setSearchValue(m_search);
1155+
setSearchValue(m_search, m_useRegex);
11441156
}
11451157
if (!m_hoveredStacks.isEmpty()) {
11461158
hoverStacks(rootItem, m_hoveredStacks);
@@ -1204,15 +1216,16 @@ void FlameGraph::selectItem(FrameGraphicsItem* item)
12041216
setTooltipItem(item);
12051217
}
12061218

1207-
void FlameGraph::setSearchValue(const QString& value)
1219+
void FlameGraph::setSearchValue(const QString& value, bool useRegex)
12081220
{
12091221
if (!m_rootItem) {
12101222
return;
12111223
}
12121224

12131225
m_search = value;
1214-
1215-
auto match = applySearch(m_rootItem, value);
1226+
m_useRegex = useRegex;
1227+
auto regex = useRegex ? value : QRegularExpression::escape(value);
1228+
auto match = applySearch(m_rootItem, QRegularExpression(regex));
12161229

12171230
if (value.isEmpty()) {
12181231
m_searchResultsLabel->hide();

src/flamegraph.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class FlameGraph : public QWidget
4545

4646
private slots:
4747
void setData(FrameGraphicsItem* rootItem);
48-
void setSearchValue(const QString& value);
48+
void setSearchValue(const QString& value, bool useRegex);
4949
void navigateBack();
5050
void navigateForward();
5151

@@ -88,6 +88,7 @@ private slots:
8888
bool m_collapseRecursion = false;
8989
bool m_buildingScene = false;
9090
QString m_search;
91+
bool m_useRegex = false;
9192
// cost threshold in percent, items below that value will not be shown
9293
static const constexpr double DEFAULT_COST_THRESHOLD = 0.1;
9394
double m_costThreshold = DEFAULT_COST_THRESHOLD;

src/recordpage.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ RecordPage::RecordPage(QWidget* parent)
415415
m_recordHost->setPids(pids);
416416
});
417417

418-
ResultsUtil::connectFilter(ui->processesFilterBox, m_processProxyModel);
418+
ResultsUtil::connectFilter(ui->processesFilterBox, m_processProxyModel, ui->regexCheckBox);
419419

420420
connect(m_watcher, &QFutureWatcher<ProcDataList>::finished, this, &RecordPage::updateProcessesFinished);
421421

src/recordpage.ui

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -144,16 +144,6 @@
144144
<property name="text">
145145
<string>Process Filter:</string>
146146
</property>
147-
<property name="buddy">
148-
<cstring>processesFilterBox</cstring>
149-
</property>
150-
</widget>
151-
</item>
152-
<item row="0" column="1">
153-
<widget class="QLineEdit" name="processesFilterBox">
154-
<property name="toolTip">
155-
<string>Filter the process list by process name or process ID</string>
156-
</property>
157147
</widget>
158148
</item>
159149
<item row="1" column="0">
@@ -185,6 +175,38 @@
185175
</property>
186176
</widget>
187177
</item>
178+
<item row="0" column="1">
179+
<widget class="QWidget" name="widget_2" native="true">
180+
<layout class="QHBoxLayout" name="horizontalLayout_4">
181+
<property name="leftMargin">
182+
<number>0</number>
183+
</property>
184+
<property name="topMargin">
185+
<number>0</number>
186+
</property>
187+
<property name="rightMargin">
188+
<number>0</number>
189+
</property>
190+
<property name="bottomMargin">
191+
<number>0</number>
192+
</property>
193+
<item>
194+
<widget class="QLineEdit" name="processesFilterBox">
195+
<property name="toolTip">
196+
<string>Filter the process list by process name or process ID</string>
197+
</property>
198+
</widget>
199+
</item>
200+
<item>
201+
<widget class="QCheckBox" name="regexCheckBox">
202+
<property name="text">
203+
<string>Regex Search</string>
204+
</property>
205+
</widget>
206+
</item>
207+
</layout>
208+
</widget>
209+
</item>
188210
</layout>
189211
</widget>
190212
</item>

src/resultsbottomuppage.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ ResultsBottomUpPage::ResultsBottomUpPage(FilterAndZoomStack* filterStack, PerfPa
6161
ui->setupUi(this);
6262

6363
auto bottomUpCostModel = new BottomUpModel(this);
64-
ResultsUtil::setupTreeView(ui->bottomUpTreeView, contextMenu, ui->bottomUpSearch, bottomUpCostModel);
64+
ResultsUtil::setupTreeView(ui->bottomUpTreeView, contextMenu, ui->bottomUpSearch, ui->regexCheckBox,
65+
bottomUpCostModel);
6566
ResultsUtil::setupCostDelegate(bottomUpCostModel, ui->bottomUpTreeView);
6667
ResultsUtil::setupContextMenu(ui->bottomUpTreeView, contextMenu, bottomUpCostModel, filterStack, this);
6768

src/resultsbottomuppage.ui

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,14 @@
6767
<item>
6868
<widget class="QLineEdit" name="bottomUpSearch">
6969
<property name="toolTip">
70-
<string>Filter the call graph tree.</string>
70+
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Filter the call graph tree.&lt;br/&gt;Prefix with '%' to turn it into an regex.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
71+
</property>
72+
</widget>
73+
</item>
74+
<item>
75+
<widget class="QCheckBox" name="regexCheckBox">
76+
<property name="text">
77+
<string>Regex Search</string>
7178
</property>
7279
</widget>
7380
</item>

src/resultscallercalleepage.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ ResultsCallerCalleePage::ResultsCallerCalleePage(FilterAndZoomStack* filterStack
8484
m_callerCalleeProxy = new CallerCalleeProxy<CallerCalleeModel>(this);
8585
m_callerCalleeProxy->setSourceModel(m_callerCalleeCostModel);
8686
m_callerCalleeProxy->setSortRole(CallerCalleeModel::SortRole);
87-
ResultsUtil::connectFilter(ui->callerCalleeFilter, m_callerCalleeProxy);
87+
ResultsUtil::connectFilter(ui->callerCalleeFilter, m_callerCalleeProxy, ui->regexCheckBox);
8888
ui->callerCalleeTableView->setSortingEnabled(true);
8989
ui->callerCalleeTableView->setModel(m_callerCalleeProxy);
9090
ResultsUtil::setupContextMenu(ui->callerCalleeTableView, contextMenu, m_callerCalleeCostModel, filterStack, this,

src/resultscallercalleepage.ui

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@
7171
</property>
7272
</widget>
7373
</item>
74+
<item>
75+
<widget class="QCheckBox" name="regexCheckBox">
76+
<property name="text">
77+
<string>Regex Search</string>
78+
</property>
79+
</widget>
80+
</item>
7481
</layout>
7582
</widget>
7683
</item>

src/resultstopdownpage.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ ResultsTopDownPage::ResultsTopDownPage(FilterAndZoomStack* filterStack, PerfPars
2222
ui->setupUi(this);
2323

2424
auto topDownCostModel = new TopDownModel(this);
25-
ResultsUtil::setupTreeView(ui->topDownTreeView, contextMenu, ui->topDownSearch, topDownCostModel);
25+
ResultsUtil::setupTreeView(ui->topDownTreeView, contextMenu, ui->topDownSearch, ui->regexCheckBox,
26+
topDownCostModel);
2627
ResultsUtil::setupCostDelegate(topDownCostModel, ui->topDownTreeView);
2728
ResultsUtil::setupContextMenu(ui->topDownTreeView, contextMenu, topDownCostModel, filterStack, this);
2829

src/resultstopdownpage.ui

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,14 @@
6767
<item>
6868
<widget class="QLineEdit" name="topDownSearch">
6969
<property name="toolTip">
70-
<string>Filter the call graph tree.</string>
70+
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Filter the call graph tree.&lt;br/&gt;Prefix with '%' to turn it into an regex.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
71+
</property>
72+
</widget>
73+
</item>
74+
<item>
75+
<widget class="QCheckBox" name="regexCheckBox">
76+
<property name="text">
77+
<string>Regex Search</string>
7178
</property>
7279
</widget>
7380
</item>

src/resultsutil.cpp

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "resultsutil.h"
1010

11+
#include <QCheckBox>
1112
#include <QComboBox>
1213
#include <QCoreApplication>
1314
#include <QHeaderView>
@@ -33,8 +34,9 @@ void setupHeaderView(QTreeView* view, CostContextMenu* contextMenu)
3334
view->setHeader(new CostHeaderView(contextMenu, view));
3435
}
3536

36-
void connectFilter(QLineEdit* filter, QSortFilterProxyModel* proxy)
37+
void connectFilter(QLineEdit* filter, QSortFilterProxyModel* proxy, QCheckBox* regexCheckBox)
3738
{
39+
Q_ASSERT(regexCheckBox);
3840
auto* timer = new QTimer(filter);
3941
timer->setSingleShot(true);
4042

@@ -44,17 +46,28 @@ void connectFilter(QLineEdit* filter, QSortFilterProxyModel* proxy)
4446
proxy->setFilterKeyColumn(-1);
4547
proxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
4648

47-
QObject::connect(timer, &QTimer::timeout, proxy, [filter, proxy]() {
48-
proxy->setFilterRegularExpression(QRegularExpression::escape(filter->text()));
49-
});
49+
auto setFilterNeedle = [filter, proxy, regexCheckBox]() {
50+
auto useRegex = regexCheckBox->isChecked();
51+
const auto needle = filter->text();
52+
proxy->setFilterRegularExpression(useRegex ? needle : QRegularExpression::escape(needle));
53+
};
54+
55+
QObject::connect(timer, &QTimer::timeout, proxy, setFilterNeedle);
56+
if (regexCheckBox) {
57+
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
58+
QObject::connect(regexCheckBox, &QCheckBox::stateChanged, proxy, setFilterNeedle);
59+
#else
60+
QObject::connect(regexCheckBox, &QCheckBox::checkStateChanged, proxy, setFilterNeedle);
61+
#endif
62+
}
5063
QObject::connect(filter, &QLineEdit::textChanged, timer, [timer]() { timer->start(300); });
5164
}
5265

53-
void setupTreeView(QTreeView* view, CostContextMenu* contextMenu, QLineEdit* filter, QSortFilterProxyModel* model,
54-
int initialSortColumn, int sortRole)
66+
void setupTreeView(QTreeView* view, CostContextMenu* contextMenu, QLineEdit* filter, QCheckBox* regexSearchCheckbox,
67+
QSortFilterProxyModel* model, int initialSortColumn, int sortRole)
5568
{
5669
model->setSortRole(sortRole);
57-
connectFilter(filter, model);
70+
connectFilter(filter, model, regexSearchCheckbox);
5871

5972
view->setModel(model);
6073
setupHeaderView(view, contextMenu);

src/resultsutil.h

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class QComboBox;
1919
class QLineEdit;
2020
class QSortFilterProxyModel;
2121
class QAbstractItemModel;
22+
class QCheckBox;
2223

2324
namespace Data {
2425
class Costs;
@@ -31,18 +32,19 @@ class CostContextMenu;
3132
namespace ResultsUtil {
3233
void setupHeaderView(QTreeView* view, CostContextMenu* contextMenu);
3334

34-
void connectFilter(QLineEdit* filter, QSortFilterProxyModel* proxy);
35+
void connectFilter(QLineEdit* filter, QSortFilterProxyModel* proxy, QCheckBox* regexCheckBox);
3536

36-
void setupTreeView(QTreeView* view, CostContextMenu* contextMenu, QLineEdit* filter, QSortFilterProxyModel* model,
37-
int initialSortColumn, int sortRole);
37+
void setupTreeView(QTreeView* view, CostContextMenu* contextMenu, QLineEdit* filter, QCheckBox* regexSearchCheckBox,
38+
QSortFilterProxyModel* model, int initialSortColumn, int sortRole);
3839

3940
template<typename Model>
40-
void setupTreeView(QTreeView* view, CostContextMenu* costContextMenu, QLineEdit* filter, Model* model)
41+
void setupTreeView(QTreeView* view, CostContextMenu* costContextMenu, QLineEdit* filter, QCheckBox* regexSearchCheckBox,
42+
Model* model)
4143
{
4244
auto* proxy = new CostProxy<Model>(view);
4345
proxy->setSourceModel(model);
44-
setupTreeView(view, costContextMenu, filter, qobject_cast<QSortFilterProxyModel*>(proxy), Model::InitialSortColumn,
45-
Model::SortRole);
46+
setupTreeView(view, costContextMenu, filter, regexSearchCheckBox, qobject_cast<QSortFilterProxyModel*>(proxy),
47+
Model::InitialSortColumn, Model::SortRole);
4648
}
4749

4850
void setupCostDelegate(QAbstractItemModel* model, QTreeView* view, int sortRole, int totalCostRole, int numBaseColumns);

src/timelinewidget.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ TimeLineWidget::TimeLineWidget(PerfParser* parser, QMenu* filterMenu, FilterAndZ
6767
timeLineProxy->setSortRole(EventModel::SortRole);
6868
timeLineProxy->setFilterKeyColumn(EventModel::ThreadColumn);
6969
timeLineProxy->setFilterRole(Qt::DisplayRole);
70-
ResultsUtil::connectFilter(ui->timeLineSearch, timeLineProxy);
70+
ResultsUtil::connectFilter(ui->timeLineSearch, timeLineProxy, ui->regexCheckBox);
7171
ui->timeLineView->setModel(timeLineProxy);
7272
ui->timeLineView->setSortingEnabled(true);
7373
// ensure the vertical scroll bar is always shown, otherwise the timeline

src/timelinewidget.ui

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@
6464
</property>
6565
</widget>
6666
</item>
67+
<item>
68+
<widget class="QCheckBox" name="regexCheckBox">
69+
<property name="text">
70+
<string>Regex Search</string>
71+
</property>
72+
</widget>
73+
</item>
6774
<item>
6875
<widget class="QLabel" name="label_2">
6976
<property name="text">

0 commit comments

Comments
 (0)