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"