Skip to content

Commit d8a8964

Browse files
authored
Fix: infinite loop and crash in search bar (#3574)
1 parent 9cd2b43 commit d8a8964

2 files changed

Lines changed: 55 additions & 25 deletions

File tree

src/widgets/SearchableTextEdit.cpp

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ SearchableTextEdit::SearchableTextEdit(QWidget *parent) : QPlainTextEdit(parent)
88

99
QPair<int, int> SearchableTextEdit::search(const QString &string, int options)
1010
{
11-
m_searchCursors.clear();
11+
m_searchResults.clear();
1212
m_currentIndex = -1;
13-
m_searchedMatchLen = -1;
1413
m_highlightMatches = options & SearchOption::HighlightMatches;
1514

1615
if (string.isEmpty()) {
@@ -34,7 +33,12 @@ QPair<int, int> SearchableTextEdit::search(const QString &string, int options)
3433
const QRegularExpression regex(string);
3534
while (!cursor.isNull() && !cursor.atEnd()) {
3635
cursor = doc->find(regex, cursor, flags);
37-
handleMatch(cursor, originalCursor);
36+
37+
if (cursor.hasSelection()) {
38+
handleMatch(cursor, originalCursor);
39+
} else {
40+
cursor.movePosition(QTextCursor::NextCharacter); // avoid infinite loop
41+
}
3842
}
3943
} else {
4044
while (!cursor.isNull() && !cursor.atEnd()) {
@@ -44,14 +48,14 @@ QPair<int, int> SearchableTextEdit::search(const QString &string, int options)
4448
}
4549

4650
// wrap around
47-
if (m_currentIndex < 0 && !m_searchCursors.isEmpty()) {
51+
if (m_currentIndex < 0 && !m_searchResults.isEmpty()) {
4852
m_currentIndex = 0;
4953
}
5054

5155
scrollToCurrentIndex();
5256
highlightMatches();
5357

54-
return { m_currentIndex, m_searchCursors.size() };
58+
return { m_currentIndex, m_searchResults.size() };
5559
}
5660

5761
void SearchableTextEdit::handleMatch(const QTextCursor &currentCursor,
@@ -60,22 +64,27 @@ void SearchableTextEdit::handleMatch(const QTextCursor &currentCursor,
6064
if (!currentCursor.isNull()) {
6165

6266
if (m_currentIndex < 0 && currentCursor.selectionEnd() >= originalCursor.selectionStart()) {
63-
m_currentIndex = m_searchCursors.size();
67+
m_currentIndex = m_searchResults.size();
6468
}
6569

66-
m_searchCursors.append(currentCursor);
70+
int len = currentCursor.selectionEnd() - currentCursor.selectionStart();
71+
m_searchResults.append(SearchResult { currentCursor.selectionStart(), len });
6772
}
6873
}
6974

7075
void SearchableTextEdit::clearSearch()
7176
{
7277
this->setExtraSelections({});
73-
m_searchCursors.clear();
78+
m_searchResults.clear();
7479
}
7580

7681
int SearchableTextEdit::findNext()
7782
{
78-
m_currentIndex = (m_currentIndex + 1) % m_searchCursors.size();
83+
if (m_searchResults.isEmpty()) {
84+
return 0;
85+
}
86+
87+
m_currentIndex = (m_currentIndex + 1) % m_searchResults.size();
7988

8089
scrollToCurrentIndex();
8190
highlightMatches();
@@ -85,7 +94,11 @@ int SearchableTextEdit::findNext()
8594

8695
int SearchableTextEdit::findPrev()
8796
{
88-
int count = m_searchCursors.size();
97+
if (m_searchResults.isEmpty()) {
98+
return 0;
99+
}
100+
101+
int count = m_searchResults.size();
89102
m_currentIndex = (m_currentIndex - 1 + count) % count;
90103
scrollToCurrentIndex();
91104
highlightMatches();
@@ -95,7 +108,11 @@ int SearchableTextEdit::findPrev()
95108

96109
int SearchableTextEdit::findLast()
97110
{
98-
m_currentIndex = m_searchCursors.size() - 1;
111+
if (m_searchResults.isEmpty()) {
112+
return 0;
113+
}
114+
115+
m_currentIndex = m_searchResults.size() - 1;
99116

100117
scrollToCurrentIndex();
101118
highlightMatches();
@@ -111,13 +128,15 @@ void SearchableTextEdit::resizeEvent(QResizeEvent *event)
111128

112129
void SearchableTextEdit::highlightMatches()
113130
{
114-
if (m_currentIndex < 0 || m_currentIndex >= m_searchCursors.size()) {
131+
if (m_currentIndex < 0 || m_currentIndex >= m_searchResults.size()) {
115132
this->setExtraSelections({});
116133
return;
117134
}
118135

136+
QTextCursor cursor(this->document());
137+
119138
if (!m_highlightMatches) {
120-
const QTextCursor cursor = m_searchCursors[m_currentIndex];
139+
mapCursorToResult(cursor, m_searchResults[m_currentIndex]);
121140
QTextEdit::ExtraSelection selection;
122141
selection.format.setBackground(ConfigColor("searchCurrent"));
123142
selection.cursor = cursor;
@@ -127,26 +146,25 @@ void SearchableTextEdit::highlightMatches()
127146

128147
QPoint startPoint = QPoint(0, 0);
129148
int startPos = this->cursorForPosition(startPoint).position();
130-
startPos -= m_searchedMatchLen;
131-
startPos = startPos >= 0 ? startPos : 0;
132149

133150
QPoint endPoint = QPoint(this->geometry().width(), this->geometry().height());
134151
auto endCursor = this->cursorForPosition(endPoint);
135152
int endPos = endCursor.position();
136-
endPos += m_searchedMatchLen;
137153
endCursor.movePosition(QTextCursor::End);
138-
endPos = endPos <= endCursor.position() ? endPos : endCursor.position();
154+
int maxPos = endCursor.position();
139155

140156
QList<QTextEdit::ExtraSelection> selections;
141-
for (int i = 0; i < m_searchCursors.size(); ++i) {
142-
const QTextCursor cursor = m_searchCursors[i];
143-
int pos = cursor.selectionStart();
157+
for (int i = 0; i < m_searchResults.size(); ++i) {
158+
const auto res = m_searchResults[i];
159+
mapCursorToResult(cursor, m_searchResults[i]);
160+
int sPos = std::max(startPos - res.length, 0);
161+
int ePos = std::min(endPos + res.length, maxPos);
144162
if (i == m_currentIndex) {
145163
QTextEdit::ExtraSelection selection;
146164
selection.format.setBackground(ConfigColor("searchCurrent"));
147165
selection.cursor = cursor;
148166
selections.append(selection);
149-
} else if (pos >= startPos && pos < endPos) {
167+
} else if (cursor.selectionStart() >= sPos && cursor.selectionEnd() <= ePos) {
150168
// only highlight visible matches
151169
QTextEdit::ExtraSelection selection;
152170
selection.format.setBackground(ConfigColor("searchHighlight"));
@@ -159,11 +177,12 @@ void SearchableTextEdit::highlightMatches()
159177

160178
void SearchableTextEdit::scrollToCurrentIndex()
161179
{
162-
if (m_currentIndex < 0 || m_currentIndex >= m_searchCursors.size()) {
180+
if (m_currentIndex < 0 || m_currentIndex >= m_searchResults.size()) {
163181
return;
164182
}
165183

166-
QTextCursor scrollCursor = m_searchCursors[m_currentIndex];
184+
QTextCursor scrollCursor(this->document());
185+
mapCursorToResult(scrollCursor, m_searchResults[m_currentIndex]);
167186
scrollCursor.setPosition(scrollCursor.selectionStart());
168187
scrollCursor.clearSelection();
169188
this->setTextCursor(scrollCursor);

src/widgets/SearchableTextEdit.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,24 @@ public slots:
3939
void highlightMatches();
4040

4141
private:
42+
struct SearchResult
43+
{
44+
int position;
45+
int length;
46+
};
47+
4248
int m_currentIndex;
43-
QList<QTextCursor> m_searchCursors;
44-
int m_searchedMatchLen;
49+
QList<SearchResult> m_searchResults;
4550
bool m_highlightMatches;
4651

4752
void handleMatch(const QTextCursor &currentCursor, const QTextCursor &originalCursor);
4853
void scrollToCurrentIndex();
54+
55+
inline void mapCursorToResult(QTextCursor &cursor, const SearchResult result)
56+
{
57+
cursor.setPosition(result.position);
58+
cursor.setPosition(result.position + result.length, QTextCursor::KeepAnchor);
59+
}
4960
};
5061

5162
#endif // SEARCHABLETEXTEDIT_H

0 commit comments

Comments
 (0)