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"