From f26dba3c5ac4cba4c6933f2d0a8004ffc6a14c97 Mon Sep 17 00:00:00 2001 From: dail8859 Date: Tue, 18 Jun 2024 11:14:23 -0400 Subject: [PATCH] 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 --- src/NotepadNext/QuickFindWidget.cpp | 154 +++++++++++++++++++--------- src/NotepadNext/QuickFindWidget.h | 20 +++- src/NotepadNext/QuickFindWidget.ui | 48 +++++---- 3 files changed, 149 insertions(+), 73 deletions(-) diff --git a/src/NotepadNext/QuickFindWidget.cpp b/src/NotepadNext/QuickFindWidget.cpp index 1deeb7589..22408a1dd 100644 --- a/src/NotepadNext/QuickFindWidget.cpp +++ b/src/NotepadNext/QuickFindWidget.cpp @@ -46,10 +46,10 @@ QuickFindWidget::QuickFindWidget(QWidget *parent) : connect(ui->lineEdit, &QLineEdit::returnPressed, this, &QuickFindWidget::returnPressed); // Any changes need to trigger a new search - connect(ui->lineEdit, &QLineEdit::textChanged, this, &QuickFindWidget::highlightAndNavigateToNextMatch); - connect(ui->buttonMatchCase, &QToolButton::toggled, this, &QuickFindWidget::highlightAndNavigateToNextMatch); - connect(ui->buttonWholeWord, &QToolButton::toggled, this, &QuickFindWidget::highlightAndNavigateToNextMatch); - connect(ui->buttonRegexp, &QToolButton::toggled, this, &QuickFindWidget::highlightAndNavigateToNextMatch); + connect(ui->lineEdit, &QLineEdit::textChanged, this, &QuickFindWidget::performNewSearch); + connect(ui->buttonMatchCase, &QToolButton::toggled, this, &QuickFindWidget::performNewSearch); + connect(ui->buttonWholeWord, &QToolButton::toggled, this, &QuickFindWidget::performNewSearch); + connect(ui->buttonRegexp, &QToolButton::toggled, this, &QuickFindWidget::performNewSearch); } QuickFindWidget::~QuickFindWidget() @@ -92,6 +92,7 @@ bool QuickFindWidget::eventFilter(QObject *obj, QEvent *event) // Use escape key to close the quick find widget if (keyEvent->key() == Qt::Key_Escape) { clearHighlights(); + clearCachedMatches(); hide(); editor->grabFocus(); } @@ -100,94 +101,141 @@ bool QuickFindWidget::eventFilter(QObject *obj, QEvent *event) return QObject::eventFilter(obj, event); } -void QuickFindWidget::highlightMatches() +void QuickFindWidget::setSearchContextColorBad() { - qInfo(Q_FUNC_INFO); + setSearchContextColor(QStringLiteral("red")); +} +void QuickFindWidget::setSearchContextColorGood() +{ + setSearchContextColor(QStringLiteral("blue")); +} + +void QuickFindWidget::performNewSearch() +{ clearHighlights(); + clearCachedMatches(); + ui->lblInfo->hide(); + // Early out if (searchText().isEmpty()) { - setSearchContextColor("blue"); + setSearchContextColorGood(); return; } prepareSearch(); - editor->setIndicatorCurrent(indicator); - - bool foundOne = false; finder->forEachMatch([&](int start, int end) { - foundOne = true; - - const int length = end - start; - - // Don't highlight 0 length matches - if (length > 0) - editor->indicatorFillRange(start, length); - - // Advance at least 1 character to prevent infinite loop + matches.append(qMakePair(start, end)); return qMax(start + 1, end); }); - if (foundOne == false) { - setSearchContextColor("red"); + if (matches.empty()) { + setSearchContextColorBad(); } else { - setSearchContextColor("blue"); + setSearchContextColorGood(); + } + + highlightMatches(); + navigateToNextMatch(false); +} + +void QuickFindWidget::highlightMatches() +{ + qInfo(Q_FUNC_INFO); + + editor->setIndicatorCurrent(indicator); + for (const auto &range : matches) { + editor->indicatorFillRange(range.first, range.second - range.first); } } +void QuickFindWidget::showWrapIndicator() +{ + FadingIndicator::showPixmap(editor, QStringLiteral(":/icons/wrapindicator.png")); +} + void QuickFindWidget::navigateToNextMatch(bool skipCurrent) { qInfo(Q_FUNC_INFO); - if (searchText().isEmpty()) { + // Early out if there are no matches + if (matches.length() == 0) { + ui->lblInfo->hide(); return; } - int startPos = INVALID_POSITION; - if (skipCurrent) { - startPos = editor->selectionEnd(); + if (currentMatchIndex != -1) { + currentMatchIndex++; + if (currentMatchIndex >= matches.length()) { + currentMatchIndex = 0; + } } else { - startPos = editor->selectionStart(); - } - - prepareSearch(); + int startPos = INVALID_POSITION; + if (skipCurrent) { + startPos = editor->selectionEnd(); + } + else { + startPos = editor->selectionStart(); + } - auto range = finder->findNext(startPos); - if (range.cpMin == INVALID_POSITION) - return; + auto it = std::lower_bound(matches.begin(), matches.end(), startPos, [](const QPair& pair, int value) { + return pair.first < value; + }); - editor->setSel(range.cpMin, range.cpMax); - editor->verticalCentreCaret(); + if (it != matches.end()) { + currentMatchIndex = std::distance(matches.begin(), it); + } else { + // Wrap back around + currentMatchIndex = 0; + } + } - if (finder->didLatestSearchWrapAround()) { - FadingIndicator::showPixmap(editor, QStringLiteral(":/icons/wrapindicator.png")); + // Search wrapped around + if (currentMatchIndex == 0) { + showWrapIndicator(); } + + goToCurrentMatch(); } void QuickFindWidget::navigateToPrevMatch() { qInfo(Q_FUNC_INFO); - if (searchText().isEmpty()) { + // Early out if there are no matches + if (matches.length() == 0) { + ui->lblInfo->hide(); return; } - prepareSearch(); - - auto range = finder->findPrev(); - if (range.cpMin == INVALID_POSITION) + if (currentMatchIndex != -1) { + currentMatchIndex--; + if (currentMatchIndex < 0) { + currentMatchIndex = matches.length() - 1; + } + } + else { + qWarning("navigateToPrevMatch() with no valid index yet"); return; + } - editor->setSel(range.cpMin, range.cpMax); - editor->verticalCentreCaret(); + // Search wrapped around + if (currentMatchIndex == matches.length() - 1) { + showWrapIndicator(); + } + + goToCurrentMatch(); } -void QuickFindWidget::highlightAndNavigateToNextMatch() +void QuickFindWidget::goToCurrentMatch() { - highlightMatches(); - navigateToNextMatch(false); + editor->setSel(matches[currentMatchIndex].first, matches[currentMatchIndex].second); + editor->verticalCentreCaret(); + + ui->lblInfo->show(); + ui->lblInfo->setText(tr("%L1/%L2").arg(currentMatchIndex + 1).arg(matches.length())); } int QuickFindWidget::computeSearchFlags() const @@ -209,7 +257,7 @@ int QuickFindWidget::computeSearchFlags() const return searchFlags; } -void QuickFindWidget::setSearchContextColor(QString color) +void QuickFindWidget::setSearchContextColor(const QString &color) { ui->lineEdit->setStyleSheet(QStringLiteral("border: 1px solid %1; padding: 2px;").arg(color)); } @@ -253,12 +301,14 @@ void QuickFindWidget::prepareSearch() void QuickFindWidget::focusIn() { ui->lineEdit->selectAll(); - highlightAndNavigateToNextMatch(); + ui->lblInfo->hide(); + performNewSearch(); } void QuickFindWidget::focusOut() { clearHighlights(); + clearCachedMatches(); hide(); } @@ -277,3 +327,9 @@ void QuickFindWidget::clearHighlights() editor->setIndicatorCurrent(indicator); editor->indicatorClearRange(0, editor->length()); } + +void QuickFindWidget::clearCachedMatches() +{ + matches.clear(); + currentMatchIndex = -1; +} diff --git a/src/NotepadNext/QuickFindWidget.h b/src/NotepadNext/QuickFindWidget.h index 6cd8287e8..a92157b7b 100644 --- a/src/NotepadNext/QuickFindWidget.h +++ b/src/NotepadNext/QuickFindWidget.h @@ -48,31 +48,41 @@ class QuickFindWidget : public QFrame bool eventFilter(QObject *obj, QEvent *event) override; private slots: - void highlightMatches(); + void performNewSearch(); void navigateToNextMatch(bool skipCurrent = true); void navigateToPrevMatch(); - void highlightAndNavigateToNextMatch(); void positionWidget(); - void prepareSearch(); - void focusIn(); void focusOut(); void returnPressed(); private: + void highlightMatches(); void clearHighlights(); + void clearCachedMatches(); + + void prepareSearch(); int computeSearchFlags() const; - void setSearchContextColor(QString color); + + void setSearchContextColorBad(); + void setSearchContextColorGood(); + void setSearchContextColor(const QString &color); + void initializeEditorIndicator(); QString searchText() const; + void goToCurrentMatch(); + void showWrapIndicator(); Ui::QuickFindWidget *ui; ScintillaNext *editor = Q_NULLPTR; Finder *finder = Q_NULLPTR; int indicator; + + QList> matches; + qsizetype currentMatchIndex = -1; }; #endif // QUICKFINDWIDGET_H diff --git a/src/NotepadNext/QuickFindWidget.ui b/src/NotepadNext/QuickFindWidget.ui index ceb6b4d93..ace19f94a 100644 --- a/src/NotepadNext/QuickFindWidget.ui +++ b/src/NotepadNext/QuickFindWidget.ui @@ -7,7 +7,7 @@ 0 0 294 - 64 + 67 @@ -19,20 +19,24 @@ true - - - 6 - + 6 - - 6 - 6 - + + + + Find... + + + true + + + + @@ -89,18 +93,24 @@ + + + + QFrame::NoFrame + + + Placeholder + + + Qt::PlainText + + + Qt::NoTextInteraction + + + - - - - Find... - - - true - - -