File indexing completed on 2024-04-28 15:30:44

0001 /*
0002     SPDX-FileCopyrightText: 2009-2010 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
0003     SPDX-FileCopyrightText: 2007 Sebastian Pipping <webmaster@hartwork.org>
0004     SPDX-FileCopyrightText: 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
0005     SPDX-FileCopyrightText: 2007 Thomas Friedrichsmeier <thomas.friedrichsmeier@ruhr-uni-bochum.de>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "katesearchbar.h"
0011 
0012 #include "kateconfig.h"
0013 #include "katedocument.h"
0014 #include "kateglobal.h"
0015 #include "katematch.h"
0016 #include "katerenderer.h"
0017 #include "kateundomanager.h"
0018 #include "kateview.h"
0019 
0020 #include <KTextEditor/DocumentCursor>
0021 #include <KTextEditor/Message>
0022 #include <KTextEditor/MovingRange>
0023 
0024 #include "ui_searchbarincremental.h"
0025 #include "ui_searchbarpower.h"
0026 
0027 #include <KColorScheme>
0028 #include <KLocalizedString>
0029 #include <KMessageBox>
0030 #include <KStandardAction>
0031 
0032 #include <QCheckBox>
0033 #include <QComboBox>
0034 #include <QCompleter>
0035 #include <QElapsedTimer>
0036 #include <QRegularExpression>
0037 #include <QShortcut>
0038 #include <QStringListModel>
0039 #include <QVBoxLayout>
0040 
0041 #include <vector>
0042 
0043 // Turn debug messages on/off here
0044 // #define FAST_DEBUG_ENABLE
0045 
0046 #ifdef FAST_DEBUG_ENABLE
0047 #define FAST_DEBUG(x) qCDebug(LOG_KTE) << x
0048 #else
0049 #define FAST_DEBUG(x)
0050 #endif
0051 
0052 using namespace KTextEditor;
0053 
0054 namespace
0055 {
0056 class AddMenuManager
0057 {
0058 private:
0059     QVector<QString> m_insertBefore;
0060     QVector<QString> m_insertAfter;
0061     QSet<QAction *> m_actionPointers;
0062     uint m_indexWalker;
0063     QMenu *m_menu;
0064 
0065 public:
0066     AddMenuManager(QMenu *parent, int expectedItemCount)
0067         : m_insertBefore(QVector<QString>(expectedItemCount))
0068         , m_insertAfter(QVector<QString>(expectedItemCount))
0069         , m_indexWalker(0)
0070         , m_menu(nullptr)
0071     {
0072         Q_ASSERT(parent != nullptr);
0073         m_menu = parent->addMenu(i18n("Add..."));
0074         if (m_menu == nullptr) {
0075             return;
0076         }
0077         m_menu->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
0078     }
0079 
0080     void enableMenu(bool enabled)
0081     {
0082         if (m_menu == nullptr) {
0083             return;
0084         }
0085         m_menu->setEnabled(enabled);
0086     }
0087 
0088     void addEntry(const QString &before,
0089                   const QString &after,
0090                   const QString &description,
0091                   const QString &realBefore = QString(),
0092                   const QString &realAfter = QString())
0093     {
0094         if (m_menu == nullptr) {
0095             return;
0096         }
0097         QAction *const action = m_menu->addAction(before + after + QLatin1Char('\t') + description);
0098         m_insertBefore[m_indexWalker] = QString(realBefore.isEmpty() ? before : realBefore);
0099         m_insertAfter[m_indexWalker] = QString(realAfter.isEmpty() ? after : realAfter);
0100         action->setData(QVariant(m_indexWalker++));
0101         m_actionPointers.insert(action);
0102     }
0103 
0104     void addSeparator()
0105     {
0106         if (m_menu == nullptr) {
0107             return;
0108         }
0109         m_menu->addSeparator();
0110     }
0111 
0112     void handle(QAction *action, QLineEdit *lineEdit)
0113     {
0114         if (!m_actionPointers.contains(action)) {
0115             return;
0116         }
0117 
0118         const int cursorPos = lineEdit->cursorPosition();
0119         const int index = action->data().toUInt();
0120         const QString &before = m_insertBefore[index];
0121         const QString &after = m_insertAfter[index];
0122         lineEdit->insert(before + after);
0123         lineEdit->setCursorPosition(cursorPos + before.count());
0124         lineEdit->setFocus();
0125     }
0126 };
0127 
0128 } // anon namespace
0129 
0130 KateSearchBar::KateSearchBar(bool initAsPower, KTextEditor::ViewPrivate *view, KateViewConfig *config)
0131     : KateViewBarWidget(true, view)
0132     , m_view(view)
0133     , m_config(config)
0134     , m_layout(new QVBoxLayout())
0135     , m_widget(nullptr)
0136     , m_incUi(nullptr)
0137     , m_incInitCursor(view->cursorPosition())
0138     , m_powerUi(nullptr)
0139     , highlightMatchAttribute(new Attribute())
0140     , highlightReplacementAttribute(new Attribute())
0141     , m_incHighlightAll(false)
0142     , m_incFromCursor(true)
0143     , m_incMatchCase(false)
0144     , m_powerMatchCase(true)
0145     , m_powerFromCursor(false)
0146     , m_powerHighlightAll(false)
0147     , m_powerMode(0)
0148 {
0149     connect(view, &KTextEditor::View::cursorPositionChanged, this, &KateSearchBar::updateIncInitCursor);
0150     connect(view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly);
0151     connect(this, &KateSearchBar::findOrReplaceAllFinished, this, &KateSearchBar::endFindOrReplaceAll);
0152 
0153     auto setSelectionChangedByUndoRedo = [this]() {
0154         m_selectionChangedByUndoRedo = true;
0155     };
0156     auto unsetSelectionChangedByUndoRedo = [this]() {
0157         m_selectionChangedByUndoRedo = false;
0158     };
0159     KateUndoManager *docUndoManager = view->doc()->undoManager();
0160     connect(docUndoManager, &KateUndoManager::undoStart, this, setSelectionChangedByUndoRedo);
0161     connect(docUndoManager, &KateUndoManager::undoEnd, this, unsetSelectionChangedByUndoRedo);
0162     connect(docUndoManager, &KateUndoManager::redoStart, this, setSelectionChangedByUndoRedo);
0163     connect(docUndoManager, &KateUndoManager::redoEnd, this, unsetSelectionChangedByUndoRedo);
0164 
0165     // When document is reloaded, disable selection-only search so that the search won't be stuck after the first search
0166     connect(view->doc(), &KTextEditor::Document::reloaded, this, [this]() {
0167         setSelectionOnly(false);
0168     });
0169 
0170     // init match attribute
0171     Attribute::Ptr mouseInAttribute(new Attribute());
0172     mouseInAttribute->setFontBold(true);
0173     highlightMatchAttribute->setDynamicAttribute(Attribute::ActivateMouseIn, mouseInAttribute);
0174 
0175     Attribute::Ptr caretInAttribute(new Attribute());
0176     caretInAttribute->setFontItalic(true);
0177     highlightMatchAttribute->setDynamicAttribute(Attribute::ActivateCaretIn, caretInAttribute);
0178 
0179     updateHighlightColors();
0180 
0181     // Modify parent
0182     QWidget *const widget = centralWidget();
0183     widget->setLayout(m_layout);
0184     m_layout->setContentsMargins(0, 0, 0, 0);
0185 
0186     // allow to have small size, for e.g. Kile
0187     setMinimumWidth(100);
0188 
0189     // Copy global to local config backup
0190     const auto searchFlags = m_config->searchFlags();
0191     m_incHighlightAll = (searchFlags & KateViewConfig::IncHighlightAll) != 0;
0192     m_incFromCursor = (searchFlags & KateViewConfig::IncFromCursor) != 0;
0193     m_incMatchCase = (searchFlags & KateViewConfig::IncMatchCase) != 0;
0194     m_powerMatchCase = (searchFlags & KateViewConfig::PowerMatchCase) != 0;
0195     m_powerFromCursor = (searchFlags & KateViewConfig::PowerFromCursor) != 0;
0196     m_powerHighlightAll = (searchFlags & KateViewConfig::PowerHighlightAll) != 0;
0197     m_powerMode = ((searchFlags & KateViewConfig::PowerModeRegularExpression) != 0)
0198         ? MODE_REGEX
0199         : (((searchFlags & KateViewConfig::PowerModeEscapeSequences) != 0)
0200                ? MODE_ESCAPE_SEQUENCES
0201                : (((searchFlags & KateViewConfig::PowerModeWholeWords) != 0) ? MODE_WHOLE_WORDS : MODE_PLAIN_TEXT));
0202 
0203     // Load one of either dialogs
0204     if (initAsPower) {
0205         enterPowerMode();
0206     } else {
0207         enterIncrementalMode();
0208     }
0209 
0210     updateSelectionOnly();
0211 }
0212 
0213 KateSearchBar::~KateSearchBar()
0214 {
0215     if (!m_cancelFindOrReplace) {
0216         // Finish/Cancel the still running job to avoid a crash
0217         endFindOrReplaceAll();
0218     }
0219 
0220     clearHighlights();
0221     delete m_layout;
0222     delete m_widget;
0223 
0224     delete m_incUi;
0225     delete m_powerUi;
0226     if (m_workingRange) {
0227         delete m_workingRange;
0228     }
0229 }
0230 
0231 void KateSearchBar::closed()
0232 {
0233     // remove search from the view bar, because it vertically bloats up the
0234     // stacked layout in KateViewBar.
0235     if (viewBar()) {
0236         viewBar()->removeBarWidget(this);
0237     }
0238 
0239     clearHighlights();
0240     m_replacement.clear();
0241     m_unfinishedSearchText.clear();
0242 }
0243 
0244 void KateSearchBar::setReplacementPattern(const QString &replacementPattern)
0245 {
0246     Q_ASSERT(isPower());
0247 
0248     if (this->replacementPattern() == replacementPattern) {
0249         return;
0250     }
0251 
0252     m_powerUi->replacement->setEditText(replacementPattern);
0253 }
0254 
0255 QString KateSearchBar::replacementPattern() const
0256 {
0257     Q_ASSERT(isPower());
0258 
0259     return m_powerUi->replacement->currentText();
0260 }
0261 
0262 void KateSearchBar::setSearchMode(KateSearchBar::SearchMode mode)
0263 {
0264     Q_ASSERT(isPower());
0265 
0266     m_powerUi->searchMode->setCurrentIndex(mode);
0267 }
0268 
0269 void KateSearchBar::findNext()
0270 {
0271     const bool found = find();
0272 
0273     if (found) {
0274         QComboBox *combo = m_powerUi != nullptr ? m_powerUi->pattern : m_incUi->pattern;
0275 
0276         // Add to search history
0277         addCurrentTextToHistory(combo);
0278     }
0279 }
0280 
0281 void KateSearchBar::findPrevious()
0282 {
0283     const bool found = find(SearchBackward);
0284 
0285     if (found) {
0286         QComboBox *combo = m_powerUi != nullptr ? m_powerUi->pattern : m_incUi->pattern;
0287 
0288         // Add to search history
0289         addCurrentTextToHistory(combo);
0290     }
0291 }
0292 
0293 void KateSearchBar::showResultMessage()
0294 {
0295     QString text;
0296 
0297     if (m_replaceMode) {
0298         text = i18ncp("short translation", "1 replacement made", "%1 replacements made", m_matchCounter);
0299     } else {
0300         text = i18ncp("short translation", "1 match found", "%1 matches found", m_matchCounter);
0301     }
0302 
0303     if (m_infoMessage) {
0304         m_infoMessage->setText(text);
0305     } else {
0306         m_infoMessage = new KTextEditor::Message(text, KTextEditor::Message::Positive);
0307         m_infoMessage->setPosition(KTextEditor::Message::BottomInView);
0308         m_infoMessage->setAutoHide(3000); // 3 seconds
0309         m_infoMessage->setView(m_view);
0310         m_view->doc()->postMessage(m_infoMessage);
0311     }
0312 }
0313 
0314 void KateSearchBar::highlightMatch(Range range)
0315 {
0316     KTextEditor::MovingRange *const highlight = m_view->doc()->newMovingRange(range, Kate::TextRange::DoNotExpand);
0317     highlight->setView(m_view); // show only in this view
0318     highlight->setAttributeOnlyForViews(true);
0319     // use z depth defined in moving ranges interface
0320     highlight->setZDepth(-10000.0);
0321     highlight->setAttribute(highlightMatchAttribute);
0322     m_hlRanges.append(highlight);
0323 }
0324 
0325 void KateSearchBar::highlightReplacement(Range range)
0326 {
0327     KTextEditor::MovingRange *const highlight = m_view->doc()->newMovingRange(range, Kate::TextRange::DoNotExpand);
0328     highlight->setView(m_view); // show only in this view
0329     highlight->setAttributeOnlyForViews(true);
0330     // use z depth defined in moving ranges interface
0331     highlight->setZDepth(-10000.0);
0332     highlight->setAttribute(highlightReplacementAttribute);
0333     m_hlRanges.append(highlight);
0334 }
0335 
0336 void KateSearchBar::indicateMatch(MatchResult matchResult)
0337 {
0338     QLineEdit *const lineEdit = isPower() ? m_powerUi->pattern->lineEdit() : m_incUi->pattern->lineEdit();
0339     QPalette background(lineEdit->palette());
0340 
0341     switch (matchResult) {
0342     case MatchFound: // FALLTHROUGH
0343     case MatchWrappedForward:
0344     case MatchWrappedBackward:
0345         // Green background for line edit
0346         KColorScheme::adjustBackground(background, KColorScheme::PositiveBackground);
0347         break;
0348     case MatchMismatch:
0349         // Red background for line edit
0350         KColorScheme::adjustBackground(background, KColorScheme::NegativeBackground);
0351         break;
0352     case MatchNothing:
0353         // Reset background of line edit
0354         background = QPalette();
0355         break;
0356     case MatchNeutral:
0357         KColorScheme::adjustBackground(background, KColorScheme::NeutralBackground);
0358         break;
0359     }
0360 
0361     // Update status label
0362     if (m_incUi != nullptr) {
0363         QPalette foreground(m_incUi->status->palette());
0364         switch (matchResult) {
0365         case MatchFound: // FALLTHROUGH
0366         case MatchNothing:
0367             KColorScheme::adjustForeground(foreground, KColorScheme::NormalText, QPalette::WindowText, KColorScheme::Window);
0368             m_incUi->status->clear();
0369             break;
0370         case MatchWrappedForward:
0371         case MatchWrappedBackward:
0372             KColorScheme::adjustForeground(foreground, KColorScheme::NormalText, QPalette::WindowText, KColorScheme::Window);
0373             if (matchResult == MatchWrappedBackward) {
0374                 m_incUi->status->setText(i18n("Reached top, continued from bottom"));
0375             } else {
0376                 m_incUi->status->setText(i18n("Reached bottom, continued from top"));
0377             }
0378             break;
0379         case MatchMismatch:
0380             KColorScheme::adjustForeground(foreground, KColorScheme::NegativeText, QPalette::WindowText, KColorScheme::Window);
0381             m_incUi->status->setText(i18n("Not found"));
0382             break;
0383         case MatchNeutral:
0384             /* do nothing */
0385             break;
0386         }
0387         m_incUi->status->setPalette(foreground);
0388     }
0389 
0390     lineEdit->setPalette(background);
0391 }
0392 
0393 /*static*/ void KateSearchBar::selectRange(KTextEditor::ViewPrivate *view, KTextEditor::Range range)
0394 {
0395     view->setCursorPositionInternal(range.end());
0396     view->setSelection(range);
0397 }
0398 
0399 void KateSearchBar::selectRange2(KTextEditor::Range range)
0400 {
0401     disconnect(m_view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly);
0402     selectRange(m_view, range);
0403     connect(m_view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly);
0404 }
0405 
0406 void KateSearchBar::onIncPatternChanged(const QString &pattern)
0407 {
0408     if (!m_incUi) {
0409         return;
0410     }
0411 
0412     // clear prior highlightings (deletes info message if present)
0413     clearHighlights();
0414 
0415     m_incUi->next->setDisabled(pattern.isEmpty());
0416     m_incUi->prev->setDisabled(pattern.isEmpty());
0417 
0418     KateMatch match(m_view->doc(), searchOptions());
0419 
0420     if (!pattern.isEmpty()) {
0421         // Find, first try
0422         const Range inputRange = KTextEditor::Range(m_incInitCursor, m_view->document()->documentEnd());
0423         match.searchText(inputRange, pattern);
0424     }
0425 
0426     const bool wrap = !match.isValid() && !pattern.isEmpty();
0427 
0428     if (wrap) {
0429         // Find, second try
0430         const KTextEditor::Range inputRange = m_view->document()->documentRange();
0431         match.searchText(inputRange, pattern);
0432     }
0433 
0434     const MatchResult matchResult = match.isValid() ? (wrap ? MatchWrappedForward : MatchFound) : pattern.isEmpty() ? MatchNothing : MatchMismatch;
0435 
0436     const Range selectionRange = pattern.isEmpty() ? Range(m_incInitCursor, m_incInitCursor) : match.isValid() ? match.range() : Range::invalid();
0437 
0438     // don't update m_incInitCursor when we move the cursor
0439     disconnect(m_view, &KTextEditor::View::cursorPositionChanged, this, &KateSearchBar::updateIncInitCursor);
0440     selectRange2(selectionRange);
0441     connect(m_view, &KTextEditor::View::cursorPositionChanged, this, &KateSearchBar::updateIncInitCursor);
0442 
0443     indicateMatch(matchResult);
0444 }
0445 
0446 void KateSearchBar::setMatchCase(bool matchCase)
0447 {
0448     if (this->matchCase() == matchCase) {
0449         return;
0450     }
0451 
0452     if (isPower()) {
0453         m_powerUi->matchCase->setChecked(matchCase);
0454     } else {
0455         m_incUi->matchCase->setChecked(matchCase);
0456     }
0457 }
0458 
0459 void KateSearchBar::onMatchCaseToggled(bool /*matchCase*/)
0460 {
0461     sendConfig();
0462 
0463     if (m_incUi != nullptr) {
0464         // Re-search with new settings
0465         const QString pattern = m_incUi->pattern->currentText();
0466         onIncPatternChanged(pattern);
0467     } else {
0468         indicateMatch(MatchNothing);
0469     }
0470 }
0471 
0472 bool KateSearchBar::matchCase() const
0473 {
0474     return isPower() ? m_powerUi->matchCase->isChecked() : m_incUi->matchCase->isChecked();
0475 }
0476 
0477 void KateSearchBar::onReturnPressed()
0478 {
0479     const Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers();
0480     const bool shiftDown = (modifiers & Qt::ShiftModifier) != 0;
0481     const bool controlDown = (modifiers & Qt::ControlModifier) != 0;
0482 
0483     if (shiftDown) {
0484         // Shift down, search backwards
0485         findPrevious();
0486     } else {
0487         // Shift up, search forwards
0488         findNext();
0489     }
0490 
0491     if (controlDown) {
0492         Q_EMIT hideMe();
0493     }
0494 }
0495 
0496 bool KateSearchBar::findOrReplace(SearchDirection searchDirection, const QString *replacement)
0497 {
0498     // What to find?
0499     if (searchPattern().isEmpty()) {
0500         return false; // == Pattern error
0501     }
0502 
0503     // don't let selectionChanged signal mess around in this routine
0504     disconnect(m_view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly);
0505 
0506     // clear previous highlights if there are any
0507     clearHighlights();
0508 
0509     const SearchOptions enabledOptions = searchOptions(searchDirection);
0510 
0511     // Where to find?
0512     Range inputRange;
0513     const Range selection = m_view->selection() ? m_view->selectionRange() : Range::invalid();
0514     if (selection.isValid()) {
0515         if (selectionOnly()) {
0516             if (m_workingRange == nullptr) {
0517                 m_workingRange =
0518                     m_view->doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight);
0519             }
0520             if (!m_workingRange->toRange().isValid()) {
0521                 // First match in selection
0522                 inputRange = selection;
0523                 // Remember selection for succeeding selection-only searches
0524                 // Elsewhere, make sure m_workingRange is invalidated when selection/search range changes
0525                 m_workingRange->setRange(selection);
0526             } else {
0527                 // The selection wasn't changed/updated by user, so we use the previous selection
0528                 // We use the selection's start/end so that the search can move forward/backward
0529                 if (searchDirection == SearchBackward) {
0530                     inputRange.setRange(m_workingRange->start(), selection.end());
0531                 } else {
0532                     inputRange.setRange(selection.start(), m_workingRange->end());
0533                 }
0534             }
0535         } else {
0536             // Next match after/before selection if a match was selected before
0537             if (searchDirection == SearchForward) {
0538                 inputRange.setRange(selection.start(), m_view->document()->documentEnd());
0539             } else {
0540                 inputRange.setRange(Cursor(0, 0), selection.end());
0541             }
0542 
0543             // Discard selection/search range previously remembered
0544             if (m_workingRange) {
0545                 delete m_workingRange;
0546                 m_workingRange = nullptr;
0547             }
0548         }
0549     } else {
0550         // No selection
0551         setSelectionOnly(false);
0552         const Cursor cursorPos = m_view->cursorPosition();
0553         if (searchDirection == SearchForward) {
0554             inputRange.setRange(cursorPos, m_view->document()->documentEnd());
0555         } else {
0556             inputRange.setRange(Cursor(0, 0), cursorPos);
0557         }
0558     }
0559     FAST_DEBUG("Search range is" << inputRange);
0560 
0561     KateMatch match(m_view->doc(), enabledOptions);
0562     Range afterReplace = Range::invalid();
0563 
0564     // Find, first try
0565     match.searchText(inputRange, searchPattern());
0566     if (match.isValid()) {
0567         if (match.range() == selection) {
0568             // Same match again
0569             if (replacement != nullptr) {
0570                 // Selection is match -> replace
0571                 KTextEditor::MovingRange *smartInputRange =
0572                     m_view->doc()->newMovingRange(inputRange, KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight);
0573                 afterReplace = match.replace(*replacement, m_view->blockSelection());
0574                 inputRange = *smartInputRange;
0575                 delete smartInputRange;
0576             }
0577 
0578             // Find, second try after old selection
0579             if (searchDirection == SearchForward) {
0580                 const Cursor start = (replacement != nullptr) ? afterReplace.end() : selection.end();
0581                 inputRange.setRange(start, inputRange.end());
0582             } else {
0583                 const Cursor end = (replacement != nullptr) ? afterReplace.start() : selection.start();
0584                 inputRange.setRange(inputRange.start(), end);
0585             }
0586 
0587             match.searchText(inputRange, searchPattern());
0588 
0589         } else if (match.isEmpty() && match.range().end() == m_view->cursorPosition()) {
0590             // valid zero-length match, e.g.: '^', '$', '\b'
0591             // advance the range to avoid looping
0592             KTextEditor::DocumentCursor zeroLenMatch(m_view->doc(), match.range().end());
0593 
0594             if (searchDirection == SearchForward) {
0595                 zeroLenMatch.move(1);
0596                 inputRange.setRange(zeroLenMatch.toCursor(), inputRange.end());
0597             } else { // SearchBackward
0598                 zeroLenMatch.move(-1);
0599                 inputRange.setRange(inputRange.start(), zeroLenMatch.toCursor());
0600             }
0601 
0602             match.searchText(inputRange, searchPattern());
0603         }
0604     }
0605 
0606     bool askWrap = !match.isValid() && (!afterReplace.isValid() || !selectionOnly());
0607     bool wrap = false;
0608     if (askWrap) {
0609         askWrap = false;
0610         wrap = true;
0611     }
0612 
0613     if (askWrap) {
0614         QString question =
0615             searchDirection == SearchForward ? i18n("Bottom of file reached. Continue from top?") : i18n("Top of file reached. Continue from bottom?");
0616         wrap = (KMessageBox::questionTwoActions(nullptr,
0617                                                 question,
0618                                                 i18n("Continue search?"),
0619                                                 KStandardGuiItem::cont(),
0620                                                 KStandardGuiItem::cancel(),
0621                                                 QStringLiteral("DoNotShowAgainContinueSearchDialog"))
0622                 == KMessageBox::PrimaryAction);
0623     }
0624     if (wrap) {
0625         m_view->showSearchWrappedHint(searchDirection == SearchBackward);
0626         if (selectionOnly() && m_workingRange != nullptr && m_workingRange->toRange().isValid()) {
0627             inputRange = m_workingRange->toRange();
0628         } else {
0629             inputRange = m_view->document()->documentRange();
0630         }
0631         match.searchText(inputRange, searchPattern());
0632     }
0633 
0634     if (match.isValid()) {
0635         selectRange2(match.range());
0636     }
0637 
0638     const MatchResult matchResult =
0639         !match.isValid() ? MatchMismatch : !wrap ? MatchFound : searchDirection == SearchForward ? MatchWrappedForward : MatchWrappedBackward;
0640     indicateMatch(matchResult);
0641 
0642     // highlight replacements if applicable
0643     if (afterReplace.isValid()) {
0644         highlightReplacement(afterReplace);
0645     }
0646 
0647     // restore connection
0648     connect(m_view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly);
0649 
0650     return true; // == No pattern error
0651 }
0652 
0653 void KateSearchBar::findAll()
0654 {
0655     // clear highlightings of prior search&replace action
0656     clearHighlights();
0657 
0658     Range inputRange = (m_view->selection() && selectionOnly()) ? m_view->selectionRange() : m_view->document()->documentRange();
0659 
0660     beginFindAll(inputRange);
0661 }
0662 
0663 void KateSearchBar::onPowerPatternChanged(const QString & /*pattern*/)
0664 {
0665     givePatternFeedback();
0666     indicateMatch(MatchNothing);
0667 }
0668 
0669 bool KateSearchBar::isPatternValid() const
0670 {
0671     if (searchPattern().isEmpty()) {
0672         return false;
0673     }
0674 
0675     return searchOptions().testFlag(WholeWords) ? searchPattern().trimmed() == searchPattern()
0676                                                 : searchOptions().testFlag(Regex)
0677                                                 ? QRegularExpression(searchPattern(), QRegularExpression::UseUnicodePropertiesOption).isValid() : true;
0678 }
0679 
0680 void KateSearchBar::givePatternFeedback()
0681 {
0682     // Enable/disable next/prev and replace next/all
0683     m_powerUi->findNext->setEnabled(isPatternValid());
0684     m_powerUi->findPrev->setEnabled(isPatternValid());
0685     m_powerUi->replaceNext->setEnabled(isPatternValid());
0686     m_powerUi->replaceAll->setEnabled(isPatternValid());
0687     m_powerUi->findAll->setEnabled(isPatternValid());
0688 }
0689 
0690 void KateSearchBar::addCurrentTextToHistory(QComboBox *combo)
0691 {
0692     const QString text = combo->currentText();
0693     const int index = combo->findText(text);
0694 
0695     if (index > 0) {
0696         combo->removeItem(index);
0697     }
0698     if (index != 0) {
0699         combo->insertItem(0, text);
0700         combo->setCurrentIndex(0);
0701     }
0702 
0703     // sync to application config
0704     KTextEditor::EditorPrivate::self()->saveSearchReplaceHistoryModels();
0705 }
0706 
0707 void KateSearchBar::backupConfig(bool ofPower)
0708 {
0709     if (ofPower) {
0710         m_powerMatchCase = m_powerUi->matchCase->isChecked();
0711         m_powerMode = m_powerUi->searchMode->currentIndex();
0712     } else {
0713         m_incMatchCase = m_incUi->matchCase->isChecked();
0714     }
0715 }
0716 
0717 void KateSearchBar::sendConfig()
0718 {
0719     const auto pastFlags = m_config->searchFlags();
0720     auto futureFlags = pastFlags;
0721 
0722     if (m_powerUi != nullptr) {
0723         const bool OF_POWER = true;
0724         backupConfig(OF_POWER);
0725 
0726         // Update power search flags only
0727         const auto incFlagsOnly = pastFlags & (KateViewConfig::IncHighlightAll | KateViewConfig::IncFromCursor | KateViewConfig::IncMatchCase);
0728 
0729         futureFlags = incFlagsOnly | (m_powerMatchCase ? KateViewConfig::PowerMatchCase : 0) | (m_powerFromCursor ? KateViewConfig::PowerFromCursor : 0)
0730             | (m_powerHighlightAll ? KateViewConfig::PowerHighlightAll : 0)
0731             | ((m_powerMode == MODE_REGEX)
0732                    ? KateViewConfig::PowerModeRegularExpression
0733                    : ((m_powerMode == MODE_ESCAPE_SEQUENCES)
0734                           ? KateViewConfig::PowerModeEscapeSequences
0735                           : ((m_powerMode == MODE_WHOLE_WORDS) ? KateViewConfig::PowerModeWholeWords : KateViewConfig::PowerModePlainText)));
0736 
0737     } else if (m_incUi != nullptr) {
0738         const bool OF_INCREMENTAL = false;
0739         backupConfig(OF_INCREMENTAL);
0740 
0741         // Update incremental search flags only
0742         const auto powerFlagsOnly = pastFlags
0743             & (KateViewConfig::PowerMatchCase | KateViewConfig::PowerFromCursor | KateViewConfig::PowerHighlightAll | KateViewConfig::PowerModeRegularExpression
0744                | KateViewConfig::PowerModeEscapeSequences | KateViewConfig::PowerModeWholeWords | KateViewConfig::PowerModePlainText);
0745 
0746         futureFlags = powerFlagsOnly | (m_incHighlightAll ? KateViewConfig::IncHighlightAll : 0) | (m_incFromCursor ? KateViewConfig::IncFromCursor : 0)
0747             | (m_incMatchCase ? KateViewConfig::IncMatchCase : 0);
0748     }
0749 
0750     // Adjust global config
0751     m_config->setSearchFlags(futureFlags);
0752 }
0753 
0754 void KateSearchBar::replaceNext()
0755 {
0756     const QString replacement = m_powerUi->replacement->currentText();
0757 
0758     if (findOrReplace(SearchForward, &replacement)) {
0759         // Never merge replace actions with other replace actions/user actions
0760         m_view->doc()->undoManager()->undoSafePoint();
0761 
0762         // Add to search history
0763         addCurrentTextToHistory(m_powerUi->pattern);
0764 
0765         // Add to replace history
0766         addCurrentTextToHistory(m_powerUi->replacement);
0767     }
0768 }
0769 
0770 // replacement == NULL --> Only highlight all matches
0771 // replacement != NULL --> Replace and highlight all matches
0772 void KateSearchBar::beginFindOrReplaceAll(Range inputRange, const QString &replacement, bool replaceMode /* = true*/)
0773 {
0774     // don't let selectionChanged signal mess around in this routine
0775     disconnect(m_view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly);
0776     // Cancel job when user close the document to avoid crash
0777     connect(m_view->doc(), &KTextEditor::Document::aboutToClose, this, &KateSearchBar::endFindOrReplaceAll);
0778 
0779     if (m_powerUi) {
0780         // Offer Cancel button and disable not useful buttons
0781         m_powerUi->searchCancelStacked->setCurrentIndex(m_powerUi->searchCancelStacked->indexOf(m_powerUi->cancelPage));
0782         m_powerUi->findNext->setEnabled(false);
0783         m_powerUi->findPrev->setEnabled(false);
0784         m_powerUi->replaceNext->setEnabled(false);
0785     }
0786 
0787     m_highlightRanges.clear();
0788     m_inputRange = inputRange;
0789     m_workingRange = m_view->doc()->newMovingRange(m_inputRange);
0790     m_replacement = replacement;
0791     m_replaceMode = replaceMode;
0792     m_matchCounter = 0;
0793     m_cancelFindOrReplace = false; // Ensure we have a GO!
0794 
0795     findOrReplaceAll();
0796 }
0797 
0798 void KateSearchBar::findOrReplaceAll()
0799 {
0800     const SearchOptions enabledOptions = searchOptions(SearchForward);
0801 
0802     // we highlight all ranges of a replace, up to some hard limit
0803     // e.g. if you replace 100000 things, rendering will break down otherwise ;=)
0804     const int maxHighlightings = 65536;
0805 
0806     // reuse match object to avoid massive moving range creation
0807     KateMatch match(m_view->doc(), enabledOptions);
0808 
0809     bool block = m_view->selection() && m_view->blockSelection();
0810 
0811     int line = m_inputRange.start().line();
0812 
0813     bool timeOut = false;
0814     bool done = false;
0815 
0816     // This variable holds the number of lines that we have searched
0817     // When it reaches 50K, we break the loop to allow event processing
0818     int numLinesSearched = 0;
0819     // Use a simple range in the loop to avoid needless work
0820     KTextEditor::Range workingRangeCopy = m_workingRange->toRange();
0821     do {
0822         if (block) {
0823             delete m_workingRange; // Never forget that!
0824             m_workingRange = m_view->doc()->newMovingRange(m_view->doc()->rangeOnLine(m_inputRange, line));
0825             workingRangeCopy = m_workingRange->toRange();
0826         }
0827 
0828         do {
0829             int currentSearchLine = workingRangeCopy.start().line();
0830             match.searchText(workingRangeCopy, searchPattern());
0831             if (!match.isValid()) {
0832                 done = true;
0833                 break;
0834             }
0835             bool const originalMatchEmpty = match.isEmpty();
0836 
0837             // Work with the match
0838             Range lastRange;
0839             if (m_replaceMode) {
0840                 if (m_matchCounter == 0) {
0841                     static_cast<KTextEditor::DocumentPrivate *>(m_view->document())->startEditing();
0842                 }
0843 
0844                 // Replace
0845                 lastRange = match.replace(m_replacement, false, ++m_matchCounter);
0846                 // update working range as text must have changed now
0847                 workingRangeCopy = m_workingRange->toRange();
0848             } else {
0849                 lastRange = match.range();
0850                 ++m_matchCounter;
0851             }
0852 
0853             // remember ranges if limit not reached
0854             if (m_matchCounter < maxHighlightings) {
0855                 m_highlightRanges.push_back(lastRange);
0856             } else {
0857                 m_highlightRanges.clear();
0858                 // TODO Info user that highlighting is disabled
0859             }
0860 
0861             // Continue after match
0862             if (lastRange.end() >= workingRangeCopy.end()) {
0863                 done = true;
0864                 break;
0865             }
0866 
0867             KTextEditor::DocumentCursor workingStart(m_view->doc(), lastRange.end());
0868 
0869             if (originalMatchEmpty) {
0870                 // Can happen for regex patterns with zero-length matches, e.g. ^, $, \b
0871                 // If we don't advance here we will loop forever...
0872                 workingStart.move(1);
0873             }
0874             workingRangeCopy.setRange(workingStart.toCursor(), workingRangeCopy.end());
0875 
0876             // Are we done?
0877             if (!workingRangeCopy.isValid() || workingStart.atEndOfDocument()) {
0878                 done = true;
0879                 break;
0880             }
0881 
0882             // Check if we have searched through 50K lines and time out.
0883             // We do this to allow the search operation to be cancelled
0884             numLinesSearched += workingRangeCopy.start().line() - currentSearchLine;
0885             timeOut = numLinesSearched >= 50000;
0886 
0887         } while (!m_cancelFindOrReplace && !timeOut);
0888 
0889     } while (!m_cancelFindOrReplace && !timeOut && block && ++line <= m_inputRange.end().line());
0890 
0891     // update m_workingRange
0892     m_workingRange->setRange(workingRangeCopy);
0893 
0894     if (done || m_cancelFindOrReplace) {
0895         Q_EMIT findOrReplaceAllFinished();
0896     } else if (timeOut) {
0897         QTimer::singleShot(0, this, &KateSearchBar::findOrReplaceAll);
0898     }
0899 
0900     showResultMessage();
0901 }
0902 
0903 void KateSearchBar::endFindOrReplaceAll()
0904 {
0905     // Don't forget to remove our "crash protector"
0906     disconnect(m_view->doc(), &KTextEditor::Document::aboutToClose, this, &KateSearchBar::endFindOrReplaceAll);
0907 
0908     // After last match
0909     if (m_matchCounter > 0) {
0910         if (m_replaceMode) {
0911             static_cast<KTextEditor::DocumentPrivate *>(m_view->document())->finishEditing();
0912         }
0913     }
0914 
0915     // Add ScrollBarMarks
0916     if (!m_highlightRanges.empty()) {
0917         KTextEditor::MarkInterfaceV2 *iface = qobject_cast<KTextEditor::MarkInterfaceV2 *>(m_view->document());
0918         if (iface) {
0919             iface->setMarkDescription(KTextEditor::MarkInterface::SearchMatch, i18n("SearchHighLight"));
0920             iface->setMarkIcon(KTextEditor::MarkInterface::SearchMatch, QIcon());
0921             for (const Range &r : m_highlightRanges) {
0922                 iface->addMark(r.start().line(), KTextEditor::MarkInterface::SearchMatch);
0923             }
0924         }
0925     }
0926 
0927     // Add highlights
0928     if (m_replaceMode) {
0929         for (const Range &r : std::as_const(m_highlightRanges)) {
0930             highlightReplacement(r);
0931         }
0932         // Never merge replace actions with other replace actions/user actions
0933         m_view->doc()->undoManager()->undoSafePoint();
0934 
0935     } else {
0936         for (const Range &r : std::as_const(m_highlightRanges)) {
0937             highlightMatch(r);
0938         }
0939         //         indicateMatch(m_matchCounter > 0 ? MatchFound : MatchMismatch); TODO
0940     }
0941 
0942     // Clean-Up the still hold MovingRange
0943     delete m_workingRange;
0944     m_workingRange = nullptr; // m_workingRange is also used elsewhere so we signify that it is now "unused"
0945 
0946     // restore connection
0947     connect(m_view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly);
0948 
0949     if (m_powerUi) {
0950         // Offer Find and Replace buttons and enable again useful buttons
0951         m_powerUi->searchCancelStacked->setCurrentIndex(m_powerUi->searchCancelStacked->indexOf(m_powerUi->searchPage));
0952         m_powerUi->findNext->setEnabled(true);
0953         m_powerUi->findPrev->setEnabled(true);
0954         m_powerUi->replaceNext->setEnabled(true);
0955 
0956         // Add to search history
0957         addCurrentTextToHistory(m_powerUi->pattern);
0958 
0959         // Add to replace history
0960         addCurrentTextToHistory(m_powerUi->replacement);
0961     }
0962 
0963     m_cancelFindOrReplace = true; // Indicate we are not running
0964 }
0965 
0966 void KateSearchBar::replaceAll()
0967 {
0968     // clear prior highlightings (deletes info message if present)
0969     clearHighlights();
0970 
0971     // What to find/replace?
0972     const QString replacement = m_powerUi->replacement->currentText();
0973 
0974     // Where to replace?
0975     const bool selected = m_view->selection();
0976     Range inputRange = (selected && selectionOnly()) ? m_view->selectionRange() : m_view->document()->documentRange();
0977 
0978     beginFindOrReplaceAll(inputRange, replacement);
0979 }
0980 
0981 void KateSearchBar::setSearchPattern(const QString &searchPattern)
0982 {
0983     if (searchPattern == this->searchPattern()) {
0984         return;
0985     }
0986 
0987     if (isPower()) {
0988         m_powerUi->pattern->setEditText(searchPattern);
0989     } else {
0990         m_incUi->pattern->setEditText(searchPattern);
0991     }
0992 }
0993 
0994 QString KateSearchBar::searchPattern() const
0995 {
0996     return (m_powerUi != nullptr) ? m_powerUi->pattern->currentText() : m_incUi->pattern->currentText();
0997 }
0998 
0999 void KateSearchBar::setSelectionOnly(bool selectionOnly)
1000 {
1001     if (this->selectionOnly() == selectionOnly) {
1002         return;
1003     }
1004 
1005     if (isPower()) {
1006         m_powerUi->selectionOnly->setChecked(selectionOnly);
1007     }
1008 }
1009 
1010 bool KateSearchBar::selectionOnly() const
1011 {
1012     return isPower() ? m_powerUi->selectionOnly->isChecked() : false;
1013 }
1014 
1015 KTextEditor::SearchOptions KateSearchBar::searchOptions(SearchDirection searchDirection) const
1016 {
1017     SearchOptions enabledOptions = KTextEditor::Default;
1018 
1019     if (!matchCase()) {
1020         enabledOptions |= CaseInsensitive;
1021     }
1022 
1023     if (searchDirection == SearchBackward) {
1024         enabledOptions |= Backwards;
1025     }
1026 
1027     if (m_powerUi != nullptr) {
1028         switch (m_powerUi->searchMode->currentIndex()) {
1029         case MODE_WHOLE_WORDS:
1030             enabledOptions |= WholeWords;
1031             break;
1032 
1033         case MODE_ESCAPE_SEQUENCES:
1034             enabledOptions |= EscapeSequences;
1035             break;
1036 
1037         case MODE_REGEX:
1038             enabledOptions |= Regex;
1039             break;
1040 
1041         case MODE_PLAIN_TEXT: // FALLTHROUGH
1042         default:
1043             break;
1044         }
1045     }
1046 
1047     return enabledOptions;
1048 }
1049 
1050 struct ParInfo {
1051     int openIndex;
1052     bool capturing;
1053     int captureNumber; // 1..9
1054 };
1055 
1056 QVector<QString> KateSearchBar::getCapturePatterns(const QString &pattern) const
1057 {
1058     QVector<QString> capturePatterns;
1059     capturePatterns.reserve(9);
1060     QStack<ParInfo> parInfos;
1061 
1062     const int inputLen = pattern.length();
1063     int input = 0; // walker index
1064     bool insideClass = false;
1065     int captureCount = 0;
1066 
1067     while (input < inputLen) {
1068         if (insideClass) {
1069             // Wait for closing, unescaped ']'
1070             if (pattern[input].unicode() == L']') {
1071                 insideClass = false;
1072             }
1073             input++;
1074         } else {
1075             switch (pattern[input].unicode()) {
1076             case L'\\':
1077                 // Skip this and any next character
1078                 input += 2;
1079                 break;
1080 
1081             case L'(':
1082                 ParInfo curInfo;
1083                 curInfo.openIndex = input;
1084                 curInfo.capturing = (input + 1 >= inputLen) || (pattern[input + 1].unicode() != '?');
1085                 if (curInfo.capturing) {
1086                     captureCount++;
1087                 }
1088                 curInfo.captureNumber = captureCount;
1089                 parInfos.push(curInfo);
1090 
1091                 input++;
1092                 break;
1093 
1094             case L')':
1095                 if (!parInfos.empty()) {
1096                     ParInfo &top = parInfos.top();
1097                     if (top.capturing && (top.captureNumber <= 9)) {
1098                         const int start = top.openIndex + 1;
1099                         const int len = input - start;
1100                         if (capturePatterns.size() < top.captureNumber) {
1101                             capturePatterns.resize(top.captureNumber);
1102                         }
1103                         capturePatterns[top.captureNumber - 1] = pattern.mid(start, len);
1104                     }
1105                     parInfos.pop();
1106                 }
1107 
1108                 input++;
1109                 break;
1110 
1111             case L'[':
1112                 input++;
1113                 insideClass = true;
1114                 break;
1115 
1116             default:
1117                 input++;
1118                 break;
1119             }
1120         }
1121     }
1122 
1123     return capturePatterns;
1124 }
1125 
1126 void KateSearchBar::showExtendedContextMenu(bool forPattern, const QPoint &pos)
1127 {
1128     // Make original menu
1129     QComboBox *comboBox = forPattern ? m_powerUi->pattern : m_powerUi->replacement;
1130     QMenu *const contextMenu = comboBox->lineEdit()->createStandardContextMenu();
1131 
1132     if (contextMenu == nullptr) {
1133         return;
1134     }
1135 
1136     bool extendMenu = false;
1137     bool regexMode = false;
1138     switch (m_powerUi->searchMode->currentIndex()) {
1139     case MODE_REGEX:
1140         regexMode = true;
1141         // FALLTHROUGH
1142 
1143     case MODE_ESCAPE_SEQUENCES:
1144         extendMenu = true;
1145         break;
1146 
1147     default:
1148         break;
1149     }
1150 
1151     AddMenuManager addMenuManager(contextMenu, 37);
1152     if (!extendMenu) {
1153         addMenuManager.enableMenu(extendMenu);
1154     } else {
1155         // Build menu
1156         if (forPattern) {
1157             if (regexMode) {
1158                 addMenuManager.addEntry(QStringLiteral("^"), QString(), i18n("Beginning of line"));
1159                 addMenuManager.addEntry(QStringLiteral("$"), QString(), i18n("End of line"));
1160                 addMenuManager.addSeparator();
1161                 addMenuManager.addEntry(QStringLiteral("."), QString(), i18n("Match any character excluding new line (by default)"));
1162                 addMenuManager.addEntry(QStringLiteral("+"), QString(), i18n("One or more occurrences"));
1163                 addMenuManager.addEntry(QStringLiteral("*"), QString(), i18n("Zero or more occurrences"));
1164                 addMenuManager.addEntry(QStringLiteral("?"), QString(), i18n("Zero or one occurrences"));
1165                 addMenuManager.addEntry(QStringLiteral("{a"),
1166                                         QStringLiteral(",b}"),
1167                                         i18n("<a> through <b> occurrences"),
1168                                         QStringLiteral("{"),
1169                                         QStringLiteral(",}"));
1170 
1171                 addMenuManager.addSeparator();
1172                 addMenuManager.addSeparator();
1173                 addMenuManager.addEntry(QStringLiteral("("), QStringLiteral(")"), i18n("Group, capturing"));
1174                 addMenuManager.addEntry(QStringLiteral("|"), QString(), i18n("Or"));
1175                 addMenuManager.addEntry(QStringLiteral("["), QStringLiteral("]"), i18n("Set of characters"));
1176                 addMenuManager.addEntry(QStringLiteral("[^"), QStringLiteral("]"), i18n("Negative set of characters"));
1177                 addMenuManager.addSeparator();
1178             }
1179         } else {
1180             addMenuManager.addEntry(QStringLiteral("\\0"), QString(), i18n("Whole match reference"));
1181             addMenuManager.addSeparator();
1182             if (regexMode) {
1183                 const QString pattern = m_powerUi->pattern->currentText();
1184                 const QVector<QString> capturePatterns = getCapturePatterns(pattern);
1185 
1186                 const int captureCount = capturePatterns.count();
1187                 for (int i = 1; i <= 9; i++) {
1188                     const QString number = QString::number(i);
1189                     const QString &captureDetails =
1190                         (i <= captureCount) ? QLatin1String(" = (") + QStringView(capturePatterns[i - 1]).left(30) + QLatin1Char(')') : QString();
1191                     addMenuManager.addEntry(QLatin1String("\\") + number, QString(), i18n("Reference") + QLatin1Char(' ') + number + captureDetails);
1192                 }
1193 
1194                 addMenuManager.addSeparator();
1195             }
1196         }
1197 
1198         addMenuManager.addEntry(QStringLiteral("\\n"), QString(), i18n("Line break"));
1199         addMenuManager.addEntry(QStringLiteral("\\t"), QString(), i18n("Tab"));
1200 
1201         if (forPattern && regexMode) {
1202             addMenuManager.addEntry(QStringLiteral("\\b"), QString(), i18n("Word boundary"));
1203             addMenuManager.addEntry(QStringLiteral("\\B"), QString(), i18n("Not word boundary"));
1204             addMenuManager.addEntry(QStringLiteral("\\d"), QString(), i18n("Digit"));
1205             addMenuManager.addEntry(QStringLiteral("\\D"), QString(), i18n("Non-digit"));
1206             addMenuManager.addEntry(QStringLiteral("\\s"), QString(), i18n("Whitespace (excluding line breaks)"));
1207             addMenuManager.addEntry(QStringLiteral("\\S"), QString(), i18n("Non-whitespace"));
1208             addMenuManager.addEntry(QStringLiteral("\\w"), QString(), i18n("Word character (alphanumerics plus '_')"));
1209             addMenuManager.addEntry(QStringLiteral("\\W"), QString(), i18n("Non-word character"));
1210         }
1211 
1212         addMenuManager.addEntry(QStringLiteral("\\0???"), QString(), i18n("Octal character 000 to 377 (2^8-1)"), QStringLiteral("\\0"));
1213         addMenuManager.addEntry(QStringLiteral("\\x{????}"), QString(), i18n("Hex character 0000 to FFFF (2^16-1)"), QStringLiteral("\\x{....}"));
1214         addMenuManager.addEntry(QStringLiteral("\\\\"), QString(), i18n("Backslash"));
1215 
1216         if (forPattern && regexMode) {
1217             addMenuManager.addSeparator();
1218             addMenuManager.addEntry(QStringLiteral("(?:E"), QStringLiteral(")"), i18n("Group, non-capturing"), QStringLiteral("(?:"));
1219             addMenuManager.addEntry(QStringLiteral("(?=E"), QStringLiteral(")"), i18n("Positive Lookahead"), QStringLiteral("(?="));
1220             addMenuManager.addEntry(QStringLiteral("(?!E"), QStringLiteral(")"), i18n("Negative lookahead"), QStringLiteral("(?!"));
1221             // variable length positive/negative lookbehind is an experimental feature in Perl 5.30
1222             // see: https://perldoc.perl.org/perlre.html
1223             // currently QRegularExpression only supports fixed-length positive/negative lookbehind (2020-03-01)
1224             addMenuManager.addEntry(QStringLiteral("(?<=E"), QStringLiteral(")"), i18n("Fixed-length positive lookbehind"), QStringLiteral("(?<="));
1225             addMenuManager.addEntry(QStringLiteral("(?<!E"), QStringLiteral(")"), i18n("Fixed-length negative lookbehind"), QStringLiteral("(?<!"));
1226         }
1227 
1228         if (!forPattern) {
1229             addMenuManager.addSeparator();
1230             addMenuManager.addEntry(QStringLiteral("\\L"), QString(), i18n("Begin lowercase conversion"));
1231             addMenuManager.addEntry(QStringLiteral("\\U"), QString(), i18n("Begin uppercase conversion"));
1232             addMenuManager.addEntry(QStringLiteral("\\E"), QString(), i18n("End case conversion"));
1233             addMenuManager.addEntry(QStringLiteral("\\l"), QString(), i18n("Lowercase first character conversion"));
1234             addMenuManager.addEntry(QStringLiteral("\\u"), QString(), i18n("Uppercase first character conversion"));
1235             addMenuManager.addEntry(QStringLiteral("\\#[#..]"), QString(), i18n("Replacement counter (for Replace All)"), QStringLiteral("\\#"));
1236         }
1237     }
1238 
1239     // Show menu
1240     QAction *const result = contextMenu->exec(comboBox->mapToGlobal(pos));
1241     if (result != nullptr) {
1242         addMenuManager.handle(result, comboBox->lineEdit());
1243     }
1244 }
1245 
1246 void KateSearchBar::onPowerModeChanged(int /*index*/)
1247 {
1248     if (m_powerUi->searchMode->currentIndex() == MODE_REGEX) {
1249         m_powerUi->matchCase->setChecked(true);
1250     }
1251 
1252     sendConfig();
1253     indicateMatch(MatchNothing);
1254 
1255     givePatternFeedback();
1256 }
1257 
1258 // static void addSecondarySelection(KTextEditor::ViewPrivate *view, KTextEditor::Range range)
1259 // {
1260 //     view->addSecondaryCursorWithSelection(range);
1261 // }
1262 
1263 void KateSearchBar::nextMatchForSelection(KTextEditor::ViewPrivate *view, SearchDirection searchDirection)
1264 {
1265     if (!view->selection()) {
1266         // Select current word so we can search for that
1267         const Cursor cursorPos = view->cursorPosition();
1268         KTextEditor::Range wordRange = view->document()->wordRangeAt(cursorPos);
1269         if (wordRange.isValid()) {
1270             selectRange(view, wordRange);
1271             return;
1272         }
1273     }
1274     if (view->selection()) {
1275         // We only want text of one of the selection ranges
1276         const QString pattern = view->doc()->text(view->selectionRange());
1277 
1278         // How to find?
1279         SearchOptions enabledOptions(KTextEditor::Default);
1280         if (searchDirection == SearchBackward) {
1281             enabledOptions |= Backwards;
1282         }
1283 
1284         // Where to find?
1285         const Range selRange = view->selectionRange();
1286         //         const Range selRange = view->lastSelectionRange();
1287         Range inputRange;
1288         if (searchDirection == SearchForward) {
1289             inputRange.setRange(selRange.end(), view->doc()->documentEnd());
1290         } else {
1291             inputRange.setRange(Cursor(0, 0), selRange.start());
1292         }
1293 
1294         // Find, first try
1295         KateMatch match(view->doc(), enabledOptions);
1296         match.searchText(inputRange, pattern);
1297 
1298         if (match.isValid()) {
1299             selectRange(view, match.range());
1300             //             addSecondarySelection(view, match.range());
1301         } else {
1302             // Find, second try
1303             m_view->showSearchWrappedHint(searchDirection == SearchBackward);
1304             if (searchDirection == SearchForward) {
1305                 inputRange.setRange(Cursor(0, 0), selRange.start());
1306             } else {
1307                 inputRange.setRange(selRange.end(), view->doc()->documentEnd());
1308             }
1309             KateMatch match2(view->doc(), enabledOptions);
1310             match2.searchText(inputRange, pattern);
1311             if (match2.isValid()) {
1312                 selectRange(view, match2.range());
1313                 //                 addSecondarySelection(view, match2.range());
1314             }
1315         }
1316     }
1317 }
1318 
1319 void KateSearchBar::enterPowerMode()
1320 {
1321     QString initialPattern;
1322     bool selectionOnly = false;
1323 
1324     // Guess settings from context: init pattern with current selection
1325     const bool selected = m_view->selection();
1326     if (selected) {
1327         const Range &selection = m_view->selectionRange();
1328         if (selection.onSingleLine()) {
1329             // ... with current selection
1330             initialPattern = m_view->selectionText();
1331         } else {
1332             // Enable selection only
1333             selectionOnly = true;
1334         }
1335     }
1336 
1337     // If there's no new selection, we'll use the existing pattern
1338     if (initialPattern.isNull()) {
1339         // Coming from power search?
1340         const bool fromReplace = (m_powerUi != nullptr) && (m_widget->isVisible());
1341         if (fromReplace) {
1342             QLineEdit *const patternLineEdit = m_powerUi->pattern->lineEdit();
1343             Q_ASSERT(patternLineEdit != nullptr);
1344             patternLineEdit->selectAll();
1345             m_powerUi->pattern->setFocus(Qt::MouseFocusReason);
1346             return;
1347         }
1348 
1349         // Coming from incremental search?
1350         const bool fromIncremental = (m_incUi != nullptr) && (m_widget->isVisible());
1351         if (fromIncremental) {
1352             initialPattern = m_incUi->pattern->currentText();
1353         } else {
1354             // Search bar probably newly opened. Reset initial replacement text to empty
1355             m_replacement.clear();
1356         }
1357     }
1358 
1359     // Create dialog
1360     const bool create = (m_powerUi == nullptr);
1361     if (create) {
1362         // Kill incremental widget
1363         if (m_incUi != nullptr) {
1364             // Backup current settings
1365             const bool OF_INCREMENTAL = false;
1366             backupConfig(OF_INCREMENTAL);
1367 
1368             // Kill widget
1369             delete m_incUi;
1370             m_incUi = nullptr;
1371             m_layout->removeWidget(m_widget);
1372             m_widget->deleteLater(); // I didn't get a crash here but for symmetrie to the other mutate slot^
1373         }
1374 
1375         // Add power widget
1376         m_widget = new QWidget(this);
1377         m_powerUi = new Ui::PowerSearchBar;
1378         m_powerUi->setupUi(m_widget);
1379         m_layout->addWidget(m_widget);
1380 
1381         // Bind to shared history models
1382         m_powerUi->pattern->setDuplicatesEnabled(false);
1383         m_powerUi->pattern->setInsertPolicy(QComboBox::InsertAtTop);
1384         m_powerUi->pattern->setMaxCount(m_config->maxHistorySize());
1385         m_powerUi->pattern->setModel(KTextEditor::EditorPrivate::self()->searchHistoryModel());
1386         m_powerUi->pattern->lineEdit()->setClearButtonEnabled(true);
1387         m_powerUi->pattern->setCompleter(nullptr);
1388         m_powerUi->replacement->setDuplicatesEnabled(false);
1389         m_powerUi->replacement->setInsertPolicy(QComboBox::InsertAtTop);
1390         m_powerUi->replacement->setMaxCount(m_config->maxHistorySize());
1391         m_powerUi->replacement->setModel(KTextEditor::EditorPrivate::self()->replaceHistoryModel());
1392         m_powerUi->replacement->lineEdit()->setClearButtonEnabled(true);
1393         m_powerUi->replacement->setCompleter(nullptr);
1394 
1395         // Filter Up/Down arrow key inputs to save unfinished search/replace text
1396         m_powerUi->pattern->installEventFilter(this);
1397         m_powerUi->replacement->installEventFilter(this);
1398 
1399         // Icons
1400         // Gnome does not seem to have all icons we want, so we use fall-back icons for those that are missing.
1401         QIcon mutateIcon = QIcon::fromTheme(QStringLiteral("games-config-options"), QIcon::fromTheme(QStringLiteral("preferences-system")));
1402         QIcon matchCaseIcon = QIcon::fromTheme(QStringLiteral("format-text-superscript"), QIcon::fromTheme(QStringLiteral("format-text-bold")));
1403         m_powerUi->mutate->setIcon(mutateIcon);
1404         m_powerUi->mutate->setChecked(true);
1405         m_powerUi->findNext->setIcon(QIcon::fromTheme(QStringLiteral("go-down-search")));
1406         m_powerUi->findPrev->setIcon(QIcon::fromTheme(QStringLiteral("go-up-search")));
1407         m_powerUi->findAll->setIcon(QIcon::fromTheme(QStringLiteral("edit-find")));
1408         m_powerUi->matchCase->setIcon(matchCaseIcon);
1409         m_powerUi->selectionOnly->setIcon(QIcon::fromTheme(QStringLiteral("edit-select-all")));
1410 
1411         // Focus proxy
1412         centralWidget()->setFocusProxy(m_powerUi->pattern);
1413     }
1414 
1415     m_powerUi->selectionOnly->setChecked(selectionOnly);
1416 
1417     // Restore previous settings
1418     if (create) {
1419         m_powerUi->matchCase->setChecked(m_powerMatchCase);
1420         m_powerUi->searchMode->setCurrentIndex(m_powerMode);
1421     }
1422 
1423     // force current index of -1 --> <cursor down> shows 1st completion entry instead of 2nd
1424     m_powerUi->pattern->setCurrentIndex(-1);
1425     m_powerUi->replacement->setCurrentIndex(-1);
1426 
1427     // Set initial search pattern
1428     QLineEdit *const patternLineEdit = m_powerUi->pattern->lineEdit();
1429     Q_ASSERT(patternLineEdit != nullptr);
1430     patternLineEdit->setText(initialPattern);
1431     patternLineEdit->selectAll();
1432 
1433     // Set initial replacement text
1434     QLineEdit *const replacementLineEdit = m_powerUi->replacement->lineEdit();
1435     Q_ASSERT(replacementLineEdit != nullptr);
1436     replacementLineEdit->setText(m_replacement);
1437 
1438     // Propagate settings (slots are still inactive on purpose)
1439     onPowerPatternChanged(initialPattern);
1440     givePatternFeedback();
1441 
1442     if (create) {
1443         // Slots
1444         connect(m_powerUi->mutate, &QToolButton::clicked, this, &KateSearchBar::enterIncrementalMode);
1445         connect(patternLineEdit, &QLineEdit::textChanged, this, &KateSearchBar::onPowerPatternChanged);
1446         connect(m_powerUi->findNext, &QToolButton::clicked, this, &KateSearchBar::findNext);
1447         connect(m_powerUi->findPrev, &QToolButton::clicked, this, &KateSearchBar::findPrevious);
1448         connect(m_powerUi->replaceNext, &QPushButton::clicked, this, &KateSearchBar::replaceNext);
1449         connect(m_powerUi->replaceAll, &QPushButton::clicked, this, &KateSearchBar::replaceAll);
1450         connect(m_powerUi->searchMode, qOverload<int>(&QComboBox::currentIndexChanged), this, &KateSearchBar::onPowerModeChanged);
1451         connect(m_powerUi->matchCase, &QToolButton::toggled, this, &KateSearchBar::onMatchCaseToggled);
1452         connect(m_powerUi->findAll, &QPushButton::clicked, this, &KateSearchBar::findAll);
1453         connect(m_powerUi->cancel, &QPushButton::clicked, this, &KateSearchBar::onPowerCancelFindOrReplace);
1454 
1455         // Make [return] in pattern line edit trigger <find next> action
1456         connect(patternLineEdit, &QLineEdit::returnPressed, this, &KateSearchBar::onReturnPressed);
1457         connect(replacementLineEdit, &QLineEdit::returnPressed, this, &KateSearchBar::replaceNext);
1458 
1459         // Hook into line edit context menus
1460         m_powerUi->pattern->setContextMenuPolicy(Qt::CustomContextMenu);
1461 
1462         connect(m_powerUi->pattern, &QComboBox::customContextMenuRequested, this, qOverload<const QPoint &>(&KateSearchBar::onPowerPatternContextMenuRequest));
1463         m_powerUi->replacement->setContextMenuPolicy(Qt::CustomContextMenu);
1464         connect(m_powerUi->replacement,
1465                 &QComboBox::customContextMenuRequested,
1466                 this,
1467                 qOverload<const QPoint &>(&KateSearchBar::onPowerReplacmentContextMenuRequest));
1468     }
1469 
1470     // Focus
1471     if (m_widget->isVisible()) {
1472         m_powerUi->pattern->setFocus(Qt::MouseFocusReason);
1473     }
1474 
1475     // move close button to right layout, ensures properly at top for both incremental + advanced mode
1476     m_powerUi->gridLayout->addWidget(closeButton(), 0, 2, 1, 1);
1477 }
1478 
1479 void KateSearchBar::enterIncrementalMode()
1480 {
1481     QString initialPattern;
1482 
1483     // Guess settings from context: init pattern with current selection
1484     const bool selected = m_view->selection();
1485     if (selected) {
1486         const Range &selection = m_view->selectionRange();
1487         if (selection.onSingleLine()) {
1488             // ... with current selection
1489             initialPattern = m_view->selectionText();
1490         }
1491     }
1492 
1493     // If there's no new selection, we'll use the existing pattern
1494     if (initialPattern.isNull()) {
1495         // Coming from incremental search?
1496         const bool fromIncremental = (m_incUi != nullptr) && (m_widget->isVisible());
1497         if (fromIncremental) {
1498             m_incUi->pattern->lineEdit()->selectAll();
1499             m_incUi->pattern->setFocus(Qt::MouseFocusReason);
1500             return;
1501         }
1502 
1503         // Coming from power search?
1504         const bool fromReplace = (m_powerUi != nullptr) && (m_widget->isVisible());
1505         if (fromReplace) {
1506             initialPattern = m_powerUi->pattern->currentText();
1507             // current text will be used as initial replacement text later
1508             m_replacement = m_powerUi->replacement->currentText();
1509         }
1510     }
1511 
1512     // Still no search pattern? Use the word under the cursor
1513     if (initialPattern.isNull()) {
1514         const KTextEditor::Cursor cursorPosition = m_view->cursorPosition();
1515         initialPattern = m_view->doc()->wordAt(cursorPosition);
1516     }
1517 
1518     // Create dialog
1519     const bool create = (m_incUi == nullptr);
1520     if (create) {
1521         // Kill power widget
1522         if (m_powerUi != nullptr) {
1523             // Backup current settings
1524             const bool OF_POWER = true;
1525             backupConfig(OF_POWER);
1526 
1527             // Kill widget
1528             delete m_powerUi;
1529             m_powerUi = nullptr;
1530             m_layout->removeWidget(m_widget);
1531             m_widget->deleteLater(); // deleteLater, because it's not a good idea too delete the widget and there for the button triggering this slot
1532         }
1533 
1534         // Add incremental widget
1535         m_widget = new QWidget(this);
1536         m_incUi = new Ui::IncrementalSearchBar;
1537         m_incUi->setupUi(m_widget);
1538         m_layout->addWidget(m_widget);
1539 
1540         // Filter Up/Down arrow key inputs to save unfinished search text
1541         m_incUi->pattern->installEventFilter(this);
1542 
1543         //         new QShortcut(KStandardShortcut::paste().primary(), m_incUi->pattern, SLOT(paste()), 0, Qt::WidgetWithChildrenShortcut);
1544         //         if (!KStandardShortcut::paste().alternate().isEmpty())
1545         //             new QShortcut(KStandardShortcut::paste().alternate(), m_incUi->pattern, SLOT(paste()), 0, Qt::WidgetWithChildrenShortcut);
1546 
1547         // Icons
1548         // Gnome does not seem to have all icons we want, so we use fall-back icons for those that are missing.
1549         QIcon mutateIcon = QIcon::fromTheme(QStringLiteral("games-config-options"), QIcon::fromTheme(QStringLiteral("preferences-system")));
1550         QIcon matchCaseIcon = QIcon::fromTheme(QStringLiteral("format-text-superscript"), QIcon::fromTheme(QStringLiteral("format-text-bold")));
1551         m_incUi->mutate->setIcon(mutateIcon);
1552         m_incUi->next->setIcon(QIcon::fromTheme(QStringLiteral("go-down-search")));
1553         m_incUi->prev->setIcon(QIcon::fromTheme(QStringLiteral("go-up-search")));
1554         m_incUi->matchCase->setIcon(matchCaseIcon);
1555 
1556         // Ensure minimum size
1557         m_incUi->pattern->setMinimumWidth(12 * m_incUi->pattern->fontMetrics().height());
1558 
1559         // Customize status area
1560         m_incUi->status->setTextElideMode(Qt::ElideLeft);
1561 
1562         // Focus proxy
1563         centralWidget()->setFocusProxy(m_incUi->pattern);
1564 
1565         m_incUi->pattern->setDuplicatesEnabled(false);
1566         m_incUi->pattern->setInsertPolicy(QComboBox::InsertAtTop);
1567         m_incUi->pattern->setMaxCount(m_config->maxHistorySize());
1568         m_incUi->pattern->setModel(KTextEditor::EditorPrivate::self()->searchHistoryModel());
1569         m_incUi->pattern->lineEdit()->setClearButtonEnabled(true);
1570         m_incUi->pattern->setCompleter(nullptr);
1571     }
1572 
1573     // Restore previous settings
1574     if (create) {
1575         m_incUi->matchCase->setChecked(m_incMatchCase);
1576     }
1577 
1578     // force current index of -1 --> <cursor down> shows 1st completion entry instead of 2nd
1579     m_incUi->pattern->setCurrentIndex(-1);
1580 
1581     // Set initial search pattern
1582     if (!create) {
1583         disconnect(m_incUi->pattern, &QComboBox::editTextChanged, this, &KateSearchBar::onIncPatternChanged);
1584     }
1585     m_incUi->pattern->setEditText(initialPattern);
1586     connect(m_incUi->pattern, &QComboBox::editTextChanged, this, &KateSearchBar::onIncPatternChanged);
1587     m_incUi->pattern->lineEdit()->selectAll();
1588 
1589     // Propagate settings (slots are still inactive on purpose)
1590     if (initialPattern.isEmpty()) {
1591         // Reset edit color
1592         indicateMatch(MatchNothing);
1593     }
1594 
1595     // Enable/disable next/prev
1596     m_incUi->next->setDisabled(initialPattern.isEmpty());
1597     m_incUi->prev->setDisabled(initialPattern.isEmpty());
1598 
1599     if (create) {
1600         // Slots
1601         connect(m_incUi->mutate, &QToolButton::clicked, this, &KateSearchBar::enterPowerMode);
1602         connect(m_incUi->pattern->lineEdit(), &QLineEdit::returnPressed, this, &KateSearchBar::onReturnPressed);
1603         connect(m_incUi->next, &QToolButton::clicked, this, &KateSearchBar::findNext);
1604         connect(m_incUi->prev, &QToolButton::clicked, this, &KateSearchBar::findPrevious);
1605         connect(m_incUi->matchCase, &QToolButton::toggled, this, &KateSearchBar::onMatchCaseToggled);
1606     }
1607 
1608     // Focus
1609     if (m_widget->isVisible()) {
1610         m_incUi->pattern->setFocus(Qt::MouseFocusReason);
1611     }
1612 
1613     // move close button to right layout, ensures properly at top for both incremental + advanced mode
1614     m_incUi->hboxLayout->addWidget(closeButton());
1615 }
1616 
1617 bool KateSearchBar::clearHighlights()
1618 {
1619     // Remove ScrollBarMarks
1620     KTextEditor::MarkInterface *iface = qobject_cast<KTextEditor::MarkInterface *>(m_view->document());
1621     if (iface) {
1622         const QHash<int, KTextEditor::Mark *> marks = iface->marks();
1623         QHashIterator<int, KTextEditor::Mark *> i(marks);
1624         while (i.hasNext()) {
1625             i.next();
1626             if (i.value()->type & KTextEditor::MarkInterface::SearchMatch) {
1627                 iface->removeMark(i.value()->line, KTextEditor::MarkInterface::SearchMatch);
1628             }
1629         }
1630     }
1631 
1632     if (m_infoMessage) {
1633         delete m_infoMessage;
1634     }
1635 
1636     if (m_hlRanges.isEmpty()) {
1637         return false;
1638     }
1639     qDeleteAll(m_hlRanges);
1640     m_hlRanges.clear();
1641     return true;
1642 }
1643 
1644 void KateSearchBar::updateHighlightColors()
1645 {
1646     const QColor foregroundColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color();
1647     const QColor &searchColor = m_view->renderer()->config()->searchHighlightColor();
1648     const QColor &replaceColor = m_view->renderer()->config()->replaceHighlightColor();
1649 
1650     // init match attribute
1651     highlightMatchAttribute->setForeground(foregroundColor);
1652     highlightMatchAttribute->setBackground(searchColor);
1653     highlightMatchAttribute->dynamicAttribute(Attribute::ActivateMouseIn)->setBackground(searchColor);
1654     highlightMatchAttribute->dynamicAttribute(Attribute::ActivateMouseIn)->setForeground(foregroundColor);
1655     highlightMatchAttribute->dynamicAttribute(Attribute::ActivateCaretIn)->setBackground(searchColor);
1656     highlightMatchAttribute->dynamicAttribute(Attribute::ActivateCaretIn)->setForeground(foregroundColor);
1657 
1658     // init replacement attribute
1659     highlightReplacementAttribute->setBackground(replaceColor);
1660     highlightReplacementAttribute->setForeground(foregroundColor);
1661 }
1662 
1663 void KateSearchBar::showEvent(QShowEvent *event)
1664 {
1665     // Update init cursor
1666     if (m_incUi != nullptr) {
1667         m_incInitCursor = m_view->cursorPosition();
1668     }
1669 
1670     // We don't want to update if a "findOrReplaceAll" is running
1671     // other we end up deleting our working range and crash
1672     if (m_cancelFindOrReplace) {
1673         updateSelectionOnly();
1674     }
1675 
1676     KateViewBarWidget::showEvent(event);
1677 }
1678 
1679 bool KateSearchBar::eventFilter(QObject *obj, QEvent *event)
1680 {
1681     QComboBox *combo = qobject_cast<QComboBox *>(obj);
1682     if (combo && event->type() == QEvent::KeyPress) {
1683         const int key = static_cast<QKeyEvent *>(event)->key();
1684         const int currentIndex = combo->currentIndex();
1685         const QString currentText = combo->currentText();
1686         QString &unfinishedText = (m_powerUi && combo == m_powerUi->replacement) ? m_replacement : m_unfinishedSearchText;
1687         if (key == Qt::Key_Up && currentIndex <= 0 && unfinishedText != currentText) {
1688             // Only restore unfinished text if we are already in the latest entry
1689             combo->setCurrentIndex(-1);
1690             combo->setCurrentText(unfinishedText);
1691         } else if (key == Qt::Key_Down || key == Qt::Key_Up) {
1692             // Only save unfinished text if it is not empty and it is modified
1693             const bool isUnfinishedSearch = (!currentText.trimmed().isEmpty() && (currentIndex == -1 || combo->itemText(currentIndex) != currentText));
1694             if (isUnfinishedSearch && unfinishedText != currentText) {
1695                 unfinishedText = currentText;
1696             }
1697         }
1698     }
1699 
1700     return QWidget::eventFilter(obj, event);
1701 }
1702 
1703 void KateSearchBar::updateSelectionOnly()
1704 {
1705     // Make sure the previous selection-only search range is not used anymore
1706     if (m_workingRange && !m_selectionChangedByUndoRedo) {
1707         delete m_workingRange;
1708         m_workingRange = nullptr;
1709     }
1710 
1711     if (m_powerUi == nullptr) {
1712         return;
1713     }
1714 
1715     // Re-init "Selection only" checkbox if power search bar open
1716     const bool selected = m_view->selection();
1717     bool selectionOnly = selected;
1718     if (selected) {
1719         Range const &selection = m_view->selectionRange();
1720         selectionOnly = !selection.onSingleLine();
1721     }
1722     m_powerUi->selectionOnly->setChecked(selectionOnly);
1723 }
1724 
1725 void KateSearchBar::updateIncInitCursor()
1726 {
1727     if (m_incUi == nullptr) {
1728         return;
1729     }
1730 
1731     // Update init cursor
1732     m_incInitCursor = m_view->cursorPosition();
1733 }
1734 
1735 void KateSearchBar::onPowerPatternContextMenuRequest(const QPoint &pos)
1736 {
1737     const bool FOR_PATTERN = true;
1738     showExtendedContextMenu(FOR_PATTERN, pos);
1739 }
1740 
1741 void KateSearchBar::onPowerPatternContextMenuRequest()
1742 {
1743     onPowerPatternContextMenuRequest(m_powerUi->pattern->mapFromGlobal(QCursor::pos()));
1744 }
1745 
1746 void KateSearchBar::onPowerReplacmentContextMenuRequest(const QPoint &pos)
1747 {
1748     const bool FOR_REPLACEMENT = false;
1749     showExtendedContextMenu(FOR_REPLACEMENT, pos);
1750 }
1751 
1752 void KateSearchBar::onPowerReplacmentContextMenuRequest()
1753 {
1754     onPowerReplacmentContextMenuRequest(m_powerUi->replacement->mapFromGlobal(QCursor::pos()));
1755 }
1756 
1757 void KateSearchBar::onPowerCancelFindOrReplace()
1758 {
1759     m_cancelFindOrReplace = true;
1760 }
1761 
1762 bool KateSearchBar::isPower() const
1763 {
1764     return m_powerUi != nullptr;
1765 }
1766 
1767 void KateSearchBar::slotReadWriteChanged()
1768 {
1769     if (!KateSearchBar::isPower()) {
1770         return;
1771     }
1772 
1773     // perhaps disable/enable
1774     m_powerUi->replaceNext->setEnabled(m_view->doc()->isReadWrite() && isPatternValid());
1775     m_powerUi->replaceAll->setEnabled(m_view->doc()->isReadWrite() && isPatternValid());
1776 }
1777 
1778 #include "moc_katesearchbar.cpp"