Index: src/search/katesearchbar.h =================================================================== --- src/search/katesearchbar.h +++ src/search/katesearchbar.h @@ -109,11 +109,12 @@ void setSelectionOnly(bool selectionOnly); void setMatchCase(bool matchCase); - // Called for and + + // Called by buttons and typically /+ shortcuts void findNext(); void findPrevious(); - void findAll(); + // PowerMode stuff + void findAll(); void replaceNext(); void replaceAll(); @@ -140,17 +141,43 @@ void updateIncInitCursor(); void onPowerPatternChanged(const QString &pattern); - void onPowerModeChanged(int index); void onPowerPatternContextMenuRequest(); void onPowerPatternContextMenuRequest(const QPoint &); void onPowerReplacmentContextMenuRequest(); void onPowerReplacmentContextMenuRequest(const QPoint &); + void onPowerCancelFindOrReplace(); + + /** + * This function do the hard search & replace work in time slice steps. + * When all is done @ref m_matchCounter is set and the signal + * @ref findOrReplaceAllFinished() is emitted. + */ + void findOrReplaceAll(); + + /** + * Restore needed settings when signal @ref findOrReplaceAllFinished() + * was received. + */ + void endFindOrReplaceAll(); + +Q_SIGNALS: + /** + * Will emitted by @ref findOrReplaceAll() when all is done. + */ + void findOrReplaceAllFinished(); private: // Helpers - bool find(SearchDirection searchDirection = SearchForward, const QString *replacement = nullptr); - int findAll(KTextEditor::Range inputRange, const QString *replacement); + bool find(SearchDirection searchDirection = SearchForward) { return findOrReplace(searchDirection, nullptr); }; + bool findOrReplace(SearchDirection searchDirection, const QString *replacement); + + /** + * The entry point to start a search & replace task. + * Set needed member variables and call @ref findOrReplaceAll() to do the work. + */ + void beginFindOrReplaceAll(KTextEditor::Range inputRange, const QString &replacement, bool replaceMode = true); + void beginFindAll(KTextEditor::Range inputRange) { beginFindOrReplaceAll(inputRange, QString(), false); }; bool isPatternValid() const; @@ -190,7 +217,13 @@ KTextEditor::Cursor m_incInitCursor; // Power search related - Ui::PowerSearchBar *m_powerUi; + Ui::PowerSearchBar *m_powerUi = nullptr; + KTextEditor::Range m_inputRange; + QString m_replacement; + uint m_matchCounter = 0; + bool m_replaceMode = false; + bool m_cancelFindOrReplace = false; + std::vector m_highlightRanges; // attribute to highlight matches with KTextEditor::Attribute::Ptr highlightMatchAttribute; Index: src/search/katesearchbar.cpp =================================================================== --- src/search/katesearchbar.cpp +++ src/search/katesearchbar.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include @@ -161,6 +162,7 @@ connect(view, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(updateIncInitCursor())); + connect(this, &KateSearchBar::findOrReplaceAllFinished, this, &KateSearchBar::endFindOrReplaceAll); // init match attribute Attribute::Ptr mouseInAttribute(new Attribute()); @@ -517,7 +519,7 @@ } } -bool KateSearchBar::find(SearchDirection searchDirection, const QString *replacement) +bool KateSearchBar::findOrReplace(SearchDirection searchDirection, const QString *replacement) { // What to find? if (searchPattern().isEmpty()) { @@ -665,12 +667,8 @@ Range inputRange = (m_view->selection() && selectionOnly()) ? m_view->selectionRange() : m_view->document()->documentRange(); - const int occurrences = findAll(inputRange, nullptr); - // send passive notification to view - showInfoMessage(i18ncp("short translation", "1 match found", "%1 matches found", occurrences)); - - indicateMatch(occurrences > 0 ? MatchFound : MatchMismatch); + beginFindAll(inputRange); } void KateSearchBar::onPowerPatternChanged(const QString & /*pattern*/) @@ -782,7 +780,7 @@ { const QString replacement = m_powerUi->replacement->currentText(); - if (find(SearchForward, &replacement)) { + if (findOrReplace(SearchForward, &replacement)) { // Never merge replace actions with other replace actions/user actions m_view->doc()->undoManager()->undoSafePoint(); @@ -794,70 +792,94 @@ } } -// replacement == NULL --> Highlight all matches +// replacement == NULL --> Only highlight all matches // replacement != NULL --> Replace and highlight all matches -int KateSearchBar::findAll(Range inputRange, const QString *replacement) +void KateSearchBar::beginFindOrReplaceAll(Range inputRange, const QString &replacement, bool replaceMode/* = true*/) { // don't let selectionChanged signal mess around in this routine disconnect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(updateSelectionOnly())); + // Offer Cancel button and disable not useful buttons + m_powerUi->searchCancelStacked->setCurrentIndex(m_powerUi->searchCancelStacked->indexOf(m_powerUi->cancelPage)); + m_powerUi->findNext->setEnabled(false); + m_powerUi->findPrev->setEnabled(false); + m_powerUi->replaceNext->setEnabled(false); + + m_inputRange = inputRange; + m_replacement = replacement; + m_replaceMode = replaceMode; + m_matchCounter = 0; + m_cancelFindOrReplace = false; // Ensure we have a GO! + + findOrReplaceAll(); +} + +void KateSearchBar::findOrReplaceAll() +{ const SearchOptions enabledOptions = searchOptions(SearchForward); const bool regexMode = enabledOptions.testFlag(Regex); const bool multiLinePattern = regexMode ? KateRegExp(searchPattern()).isMultiLine() : false; - std::unique_ptr workingRange(m_view->doc()->newMovingRange(inputRange)); - int matchCounter = 0; + std::unique_ptr workingRange(m_view->doc()->newMovingRange(m_inputRange)); // we highlight all ranges of a replace, up to some hard limit // e.g. if you replace 100000 things, rendering will break down otherwise ;=) const int maxHighlightings = 65536; - std::vector highlightRanges; // reuse match object to avoid massive moving range creation KateMatch match(m_view->doc(), enabledOptions); bool block = m_view->selection() && m_view->blockSelection(); - int line = inputRange.start().line(); + int line = m_inputRange.start().line(); + + QTime rolex; // Watchog to suspend the work after some time + rolex.start(); + bool timeOut = false; + bool done = false; do { if (block) { - workingRange.reset(m_view->doc()->newMovingRange(m_view->doc()->rangeOnLine(inputRange, line))); + workingRange.reset(m_view->doc()->newMovingRange(m_view->doc()->rangeOnLine(m_inputRange, line))); } - for (;;) { + do { match.searchText(*workingRange, searchPattern()); if (!match.isValid()) { + done = true; break; } bool const originalMatchEmpty = match.isEmpty(); // Work with the match Range lastRange; - if (replacement != nullptr) { - if (matchCounter == 0) { + if (m_replaceMode) { + if (m_matchCounter == 0) { static_cast(m_view->document())->startEditing(); } // Replace - lastRange = match.replace(*replacement, false, ++matchCounter); + lastRange = match.replace(m_replacement, false, ++m_matchCounter); } else { lastRange = match.range(); - ++matchCounter; + ++m_matchCounter; } // remember ranges if limit not reached - if (matchCounter < maxHighlightings) { - highlightRanges.push_back(lastRange); + if (m_matchCounter < maxHighlightings) { + m_highlightRanges.push_back(lastRange); } else { - highlightRanges.clear(); + m_highlightRanges.clear(); + // TODO Info user that highlighting is disabled } // Continue after match if (lastRange.end() >= workingRange->end()) { + done = true; break; } KTextEditor::DocumentCursor workingStart(m_view->doc(), lastRange.end()); + if (originalMatchEmpty) { // Can happen for regex patterns like "^". // If we don't advance here we will loop forever... @@ -871,46 +893,78 @@ // Are we done? if (!workingRange->toRange().isValid() || workingStart.atEndOfDocument()) { + done = true; break; } - } - } while (block && ++line <= inputRange.end().line()); + timeOut = rolex.elapsed() > 150; + + } while (!m_cancelFindOrReplace && !timeOut); + + } while (!m_cancelFindOrReplace && !timeOut && block && ++line <= m_inputRange.end().line()); + if (done || m_cancelFindOrReplace) { + emit findOrReplaceAllFinished(); + } else if (timeOut) { + QTimer::singleShot(0, this, &KateSearchBar::findOrReplaceAll); + } +} + +void KateSearchBar::endFindOrReplaceAll() +{ // After last match - if (matchCounter > 0) { - if (replacement != nullptr) { + if (m_matchCounter > 0) { + if (m_replaceMode) { static_cast(m_view->document())->finishEditing(); } } // Add ScrollBarMarks - if (!highlightRanges.empty()) { + if (!m_highlightRanges.empty()) { KTextEditor::MarkInterface* iface = qobject_cast(m_view->document()); if (iface) { iface->setMarkDescription(KTextEditor::MarkInterface::SearchMatch, i18n("SearchHighLight")); iface->setMarkPixmap(KTextEditor::MarkInterface::SearchMatch, QIcon().pixmap(0,0)); - for (const Range &r : highlightRanges) { + for (const Range &r : m_highlightRanges) { iface->addMark(r.start().line(), KTextEditor::MarkInterface::SearchMatch); } } } // Add highlights - if (replacement == nullptr) { - for (const Range &r : highlightRanges) { - highlightMatch(r); + if (m_replaceMode) { + for (const Range &r : qAsConst(m_highlightRanges)) { + highlightReplacement(r); } + // send passive notification to view + showInfoMessage(i18ncp("short translation", "1 replacement made", "%1 replacements made", m_matchCounter)); + + // Never merge replace actions with other replace actions/user actions + m_view->doc()->undoManager()->undoSafePoint(); + } else { - for (const Range &r : highlightRanges) { - highlightReplacement(r); + for (const Range &r : qAsConst(m_highlightRanges)) { + highlightMatch(r); } + // send passive notification to view + showInfoMessage(i18ncp("short translation", "1 match found", "%1 matches found", m_matchCounter)); +// indicateMatch(m_matchCounter > 0 ? MatchFound : MatchMismatch); TODO } // restore connection connect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(updateSelectionOnly())); - return matchCounter; + // Offer Find and Replace buttons and enable again useful buttons + m_powerUi->searchCancelStacked->setCurrentIndex(m_powerUi->searchCancelStacked->indexOf(m_powerUi->searchPage)); + m_powerUi->findNext->setEnabled(true); + m_powerUi->findPrev->setEnabled(true); + m_powerUi->replaceNext->setEnabled(true); + + // Add to search history + addCurrentTextToHistory(m_powerUi->pattern); + + // Add to replace history + addCurrentTextToHistory(m_powerUi->replacement); } void KateSearchBar::replaceAll() @@ -927,20 +981,7 @@ ? m_view->selectionRange() : m_view->document()->documentRange(); - // Pass on the hard work - int replacementsDone = findAll(inputRange, &replacement); - - // send passive notification to view - showInfoMessage(i18ncp("short translation", "1 replacement made", "%1 replacements made", replacementsDone)); - - // Never merge replace actions with other replace actions/user actions - m_view->doc()->undoManager()->undoSafePoint(); - - // Add to search history - addCurrentTextToHistory(m_powerUi->pattern); - - // Add to replace history - addCurrentTextToHistory(m_powerUi->replacement); + beginFindOrReplaceAll(inputRange, replacement); } void KateSearchBar::setSearchPattern(const QString &searchPattern) @@ -1393,6 +1434,7 @@ connect(m_powerUi->searchMode, SIGNAL(currentIndexChanged(int)), this, SLOT(onPowerModeChanged(int))); connect(m_powerUi->matchCase, SIGNAL(toggled(bool)), this, SLOT(onMatchCaseToggled(bool))); connect(m_powerUi->findAll, SIGNAL(clicked()), this, SLOT(findAll())); + connect(m_powerUi->cancel, &QPushButton::clicked, this, &KateSearchBar::onPowerCancelFindOrReplace); // Make [return] in pattern line edit trigger action connect(patternLineEdit, SIGNAL(returnPressed()), this, SLOT(onReturnPressed())); @@ -1648,6 +1690,11 @@ onPowerReplacmentContextMenuRequest(m_powerUi->replacement->mapFromGlobal(QCursor::pos())); } +void KateSearchBar::onPowerCancelFindOrReplace() +{ + m_cancelFindOrReplace = true; +} + bool KateSearchBar::isPower() const { return m_powerUi != nullptr; Index: src/search/searchbarpower.ui =================================================================== --- src/search/searchbarpower.ui +++ src/search/searchbarpower.ui @@ -7,8 +7,8 @@ 0 0 - 565 - 107 + 1037 + 144 @@ -44,85 +44,10 @@ 0 - - - - - 9 - 0 - - - - Qt::StrongFocus - - - Text to search for - - - true - - - QComboBox::AdjustToMinimumContentsLengthWithIcon - - - - - - - - - Jump to next match - - - - - - - - true - - - - - - - Jump to previous match - - - - - - - - true - - - - - - - - - - 9 - 0 - - - - Text to replace with - - - true - - - QComboBox::AdjustToMinimumContentsLengthWithIcon - - - - F&ind: + Fin&d: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -158,53 +83,13 @@ - - - - - - Match case sensitive - - - - - - - - true - - - true - - - - - - - Search in the selection only - - - - - - - - true - - - true - - - - - 6 - 6 + 0 @@ -245,6 +130,40 @@ + + + + Match case sensitive + + + + .. + + + true + + + true + + + + + + + Search in the selection only + + + + .. + + + true + + + true + + + @@ -258,6 +177,34 @@ + + + + Jump to next match + + + + .. + + + true + + + + + + + Jump to previous match + + + + .. + + + true + + + @@ -269,61 +216,129 @@ - - - Replace all matches - - - Replace &All + + + 0 + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Replace all matches + + + Replace &All + + + + + + + &Find All + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Cancel + + + + + - + + + Switch to incremental search bar + - &Find All + + + + + + + true + + + true - - - - - - - - Switch to incremental search bar + + + + + 9 + 0 + - - + + Qt::StrongFocus - - - - + + Text to search for - + true - - true + + QComboBox::AdjustToMinimumContentsLengthWithIcon - - - - Qt::Vertical + + + + + 9 + 0 + + + + Text to replace with - - - 20 - 40 - + + true + + + QComboBox::AdjustToMinimumContentsLengthWithIcon - + @@ -333,7 +348,6 @@ pattern replacement searchMode - mutate