Skip to content

Commit f26dba3

Browse files
authored
Add match count to Quick Find (#572)
This also overhauls the internals a bit and caches all the locations (since it highlights them anyways) and just uses that list to navigate. In theory the document shouldn't change while the QuickFindWidget is opened, meaning the cached search results will always be valid. Closes #546
1 parent fd6040c commit f26dba3

3 files changed

Lines changed: 149 additions & 73 deletions

File tree

src/NotepadNext/QuickFindWidget.cpp

Lines changed: 105 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ QuickFindWidget::QuickFindWidget(QWidget *parent) :
4646
connect(ui->lineEdit, &QLineEdit::returnPressed, this, &QuickFindWidget::returnPressed);
4747

4848
// Any changes need to trigger a new search
49-
connect(ui->lineEdit, &QLineEdit::textChanged, this, &QuickFindWidget::highlightAndNavigateToNextMatch);
50-
connect(ui->buttonMatchCase, &QToolButton::toggled, this, &QuickFindWidget::highlightAndNavigateToNextMatch);
51-
connect(ui->buttonWholeWord, &QToolButton::toggled, this, &QuickFindWidget::highlightAndNavigateToNextMatch);
52-
connect(ui->buttonRegexp, &QToolButton::toggled, this, &QuickFindWidget::highlightAndNavigateToNextMatch);
49+
connect(ui->lineEdit, &QLineEdit::textChanged, this, &QuickFindWidget::performNewSearch);
50+
connect(ui->buttonMatchCase, &QToolButton::toggled, this, &QuickFindWidget::performNewSearch);
51+
connect(ui->buttonWholeWord, &QToolButton::toggled, this, &QuickFindWidget::performNewSearch);
52+
connect(ui->buttonRegexp, &QToolButton::toggled, this, &QuickFindWidget::performNewSearch);
5353
}
5454

5555
QuickFindWidget::~QuickFindWidget()
@@ -92,6 +92,7 @@ bool QuickFindWidget::eventFilter(QObject *obj, QEvent *event)
9292
// Use escape key to close the quick find widget
9393
if (keyEvent->key() == Qt::Key_Escape) {
9494
clearHighlights();
95+
clearCachedMatches();
9596
hide();
9697
editor->grabFocus();
9798
}
@@ -100,94 +101,141 @@ bool QuickFindWidget::eventFilter(QObject *obj, QEvent *event)
100101
return QObject::eventFilter(obj, event);
101102
}
102103

103-
void QuickFindWidget::highlightMatches()
104+
void QuickFindWidget::setSearchContextColorBad()
104105
{
105-
qInfo(Q_FUNC_INFO);
106+
setSearchContextColor(QStringLiteral("red"));
107+
}
106108

109+
void QuickFindWidget::setSearchContextColorGood()
110+
{
111+
setSearchContextColor(QStringLiteral("blue"));
112+
}
113+
114+
void QuickFindWidget::performNewSearch()
115+
{
107116
clearHighlights();
117+
clearCachedMatches();
118+
ui->lblInfo->hide();
108119

120+
// Early out
109121
if (searchText().isEmpty()) {
110-
setSearchContextColor("blue");
122+
setSearchContextColorGood();
111123
return;
112124
}
113125

114126
prepareSearch();
115-
editor->setIndicatorCurrent(indicator);
116-
117-
bool foundOne = false;
118127
finder->forEachMatch([&](int start, int end) {
119-
foundOne = true;
120-
121-
const int length = end - start;
122-
123-
// Don't highlight 0 length matches
124-
if (length > 0)
125-
editor->indicatorFillRange(start, length);
126-
127-
// Advance at least 1 character to prevent infinite loop
128+
matches.append(qMakePair(start, end));
128129
return qMax(start + 1, end);
129130
});
130131

131-
if (foundOne == false) {
132-
setSearchContextColor("red");
132+
if (matches.empty()) {
133+
setSearchContextColorBad();
133134
}
134135
else {
135-
setSearchContextColor("blue");
136+
setSearchContextColorGood();
137+
}
138+
139+
highlightMatches();
140+
navigateToNextMatch(false);
141+
}
142+
143+
void QuickFindWidget::highlightMatches()
144+
{
145+
qInfo(Q_FUNC_INFO);
146+
147+
editor->setIndicatorCurrent(indicator);
148+
for (const auto &range : matches) {
149+
editor->indicatorFillRange(range.first, range.second - range.first);
136150
}
137151
}
138152

153+
void QuickFindWidget::showWrapIndicator()
154+
{
155+
FadingIndicator::showPixmap(editor, QStringLiteral(":/icons/wrapindicator.png"));
156+
}
157+
139158
void QuickFindWidget::navigateToNextMatch(bool skipCurrent)
140159
{
141160
qInfo(Q_FUNC_INFO);
142161

143-
if (searchText().isEmpty()) {
162+
// Early out if there are no matches
163+
if (matches.length() == 0) {
164+
ui->lblInfo->hide();
144165
return;
145166
}
146167

147-
int startPos = INVALID_POSITION;
148-
if (skipCurrent) {
149-
startPos = editor->selectionEnd();
168+
if (currentMatchIndex != -1) {
169+
currentMatchIndex++;
170+
if (currentMatchIndex >= matches.length()) {
171+
currentMatchIndex = 0;
172+
}
150173
}
151174
else {
152-
startPos = editor->selectionStart();
153-
}
154-
155-
prepareSearch();
175+
int startPos = INVALID_POSITION;
176+
if (skipCurrent) {
177+
startPos = editor->selectionEnd();
178+
}
179+
else {
180+
startPos = editor->selectionStart();
181+
}
156182

157-
auto range = finder->findNext(startPos);
158-
if (range.cpMin == INVALID_POSITION)
159-
return;
183+
auto it = std::lower_bound(matches.begin(), matches.end(), startPos, [](const QPair<int, int>& pair, int value) {
184+
return pair.first < value;
185+
});
160186

161-
editor->setSel(range.cpMin, range.cpMax);
162-
editor->verticalCentreCaret();
187+
if (it != matches.end()) {
188+
currentMatchIndex = std::distance(matches.begin(), it);
189+
} else {
190+
// Wrap back around
191+
currentMatchIndex = 0;
192+
}
193+
}
163194

164-
if (finder->didLatestSearchWrapAround()) {
165-
FadingIndicator::showPixmap(editor, QStringLiteral(":/icons/wrapindicator.png"));
195+
// Search wrapped around
196+
if (currentMatchIndex == 0) {
197+
showWrapIndicator();
166198
}
199+
200+
goToCurrentMatch();
167201
}
168202

169203
void QuickFindWidget::navigateToPrevMatch()
170204
{
171205
qInfo(Q_FUNC_INFO);
172206

173-
if (searchText().isEmpty()) {
207+
// Early out if there are no matches
208+
if (matches.length() == 0) {
209+
ui->lblInfo->hide();
174210
return;
175211
}
176212

177-
prepareSearch();
178-
179-
auto range = finder->findPrev();
180-
if (range.cpMin == INVALID_POSITION)
213+
if (currentMatchIndex != -1) {
214+
currentMatchIndex--;
215+
if (currentMatchIndex < 0) {
216+
currentMatchIndex = matches.length() - 1;
217+
}
218+
}
219+
else {
220+
qWarning("navigateToPrevMatch() with no valid index yet");
181221
return;
222+
}
182223

183-
editor->setSel(range.cpMin, range.cpMax);
184-
editor->verticalCentreCaret();
224+
// Search wrapped around
225+
if (currentMatchIndex == matches.length() - 1) {
226+
showWrapIndicator();
227+
}
228+
229+
goToCurrentMatch();
185230
}
186231

187-
void QuickFindWidget::highlightAndNavigateToNextMatch()
232+
void QuickFindWidget::goToCurrentMatch()
188233
{
189-
highlightMatches();
190-
navigateToNextMatch(false);
234+
editor->setSel(matches[currentMatchIndex].first, matches[currentMatchIndex].second);
235+
editor->verticalCentreCaret();
236+
237+
ui->lblInfo->show();
238+
ui->lblInfo->setText(tr("%L1/%L2").arg(currentMatchIndex + 1).arg(matches.length()));
191239
}
192240

193241
int QuickFindWidget::computeSearchFlags() const
@@ -209,7 +257,7 @@ int QuickFindWidget::computeSearchFlags() const
209257
return searchFlags;
210258
}
211259

212-
void QuickFindWidget::setSearchContextColor(QString color)
260+
void QuickFindWidget::setSearchContextColor(const QString &color)
213261
{
214262
ui->lineEdit->setStyleSheet(QStringLiteral("border: 1px solid %1; padding: 2px;").arg(color));
215263
}
@@ -253,12 +301,14 @@ void QuickFindWidget::prepareSearch()
253301
void QuickFindWidget::focusIn()
254302
{
255303
ui->lineEdit->selectAll();
256-
highlightAndNavigateToNextMatch();
304+
ui->lblInfo->hide();
305+
performNewSearch();
257306
}
258307

259308
void QuickFindWidget::focusOut()
260309
{
261310
clearHighlights();
311+
clearCachedMatches();
262312
hide();
263313
}
264314

@@ -277,3 +327,9 @@ void QuickFindWidget::clearHighlights()
277327
editor->setIndicatorCurrent(indicator);
278328
editor->indicatorClearRange(0, editor->length());
279329
}
330+
331+
void QuickFindWidget::clearCachedMatches()
332+
{
333+
matches.clear();
334+
currentMatchIndex = -1;
335+
}

src/NotepadNext/QuickFindWidget.h

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,31 +48,41 @@ class QuickFindWidget : public QFrame
4848
bool eventFilter(QObject *obj, QEvent *event) override;
4949

5050
private slots:
51-
void highlightMatches();
51+
void performNewSearch();
5252
void navigateToNextMatch(bool skipCurrent = true);
5353
void navigateToPrevMatch();
54-
void highlightAndNavigateToNextMatch();
5554

5655
void positionWidget();
5756

58-
void prepareSearch();
59-
6057
void focusIn();
6158
void focusOut();
6259

6360
void returnPressed();
6461

6562
private:
63+
void highlightMatches();
6664
void clearHighlights();
65+
void clearCachedMatches();
66+
67+
void prepareSearch();
6768
int computeSearchFlags() const;
68-
void setSearchContextColor(QString color);
69+
70+
void setSearchContextColorBad();
71+
void setSearchContextColorGood();
72+
void setSearchContextColor(const QString &color);
73+
6974
void initializeEditorIndicator();
7075
QString searchText() const;
76+
void goToCurrentMatch();
77+
void showWrapIndicator();
7178

7279
Ui::QuickFindWidget *ui;
7380
ScintillaNext *editor = Q_NULLPTR;
7481
Finder *finder = Q_NULLPTR;
7582
int indicator;
83+
84+
QList<QPair<int, int>> matches;
85+
qsizetype currentMatchIndex = -1;
7686
};
7787

7888
#endif // QUICKFINDWIDGET_H

src/NotepadNext/QuickFindWidget.ui

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<x>0</x>
88
<y>0</y>
99
<width>294</width>
10-
<height>64</height>
10+
<height>67</height>
1111
</rect>
1212
</property>
1313
<property name="focusPolicy">
@@ -19,20 +19,24 @@
1919
<property name="autoFillBackground">
2020
<bool>true</bool>
2121
</property>
22-
<layout class="QGridLayout" name="gridLayout">
23-
<property name="leftMargin">
24-
<number>6</number>
25-
</property>
22+
<layout class="QVBoxLayout" name="verticalLayout">
2623
<property name="topMargin">
2724
<number>6</number>
2825
</property>
29-
<property name="rightMargin">
30-
<number>6</number>
31-
</property>
3226
<property name="bottomMargin">
3327
<number>6</number>
3428
</property>
35-
<item row="1" column="0">
29+
<item>
30+
<widget class="QLineEdit" name="lineEdit">
31+
<property name="placeholderText">
32+
<string>Find...</string>
33+
</property>
34+
<property name="clearButtonEnabled">
35+
<bool>true</bool>
36+
</property>
37+
</widget>
38+
</item>
39+
<item>
3640
<layout class="QHBoxLayout" name="horizontalLayout">
3741
<item>
3842
<widget class="QToolButton" name="buttonMatchCase">
@@ -89,18 +93,24 @@
8993
</property>
9094
</spacer>
9195
</item>
96+
<item>
97+
<widget class="QLabel" name="lblInfo">
98+
<property name="frameShape">
99+
<enum>QFrame::NoFrame</enum>
100+
</property>
101+
<property name="text">
102+
<string notr="true">Placeholder</string>
103+
</property>
104+
<property name="textFormat">
105+
<enum>Qt::PlainText</enum>
106+
</property>
107+
<property name="textInteractionFlags">
108+
<set>Qt::NoTextInteraction</set>
109+
</property>
110+
</widget>
111+
</item>
92112
</layout>
93113
</item>
94-
<item row="0" column="0">
95-
<widget class="QLineEdit" name="lineEdit">
96-
<property name="placeholderText">
97-
<string>Find...</string>
98-
</property>
99-
<property name="clearButtonEnabled">
100-
<bool>true</bool>
101-
</property>
102-
</widget>
103-
</item>
104114
</layout>
105115
</widget>
106116
<resources/>

0 commit comments

Comments
 (0)