File indexing completed on 2024-04-21 03:57:40

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