KTextEditor

katesearchbar.cpp
1/*
2 SPDX-FileCopyrightText: 2009-2010 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
3 SPDX-FileCopyrightText: 2007 Sebastian Pipping <webmaster@hartwork.org>
4 SPDX-FileCopyrightText: 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
5 SPDX-FileCopyrightText: 2007 Thomas Friedrichsmeier <thomas.friedrichsmeier@ruhr-uni-bochum.de>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "katesearchbar.h"
11
12#include "kateconfig.h"
13#include "katedocument.h"
14#include "kateglobal.h"
15#include "katematch.h"
16#include "kateundomanager.h"
17#include "kateview.h"
18
19#include <KTextEditor/DocumentCursor>
20#include <KTextEditor/Message>
21#include <KTextEditor/MovingRange>
22
23#include "ui_searchbarincremental.h"
24#include "ui_searchbarpower.h"
25
26#include <KColorScheme>
27#include <KLocalizedString>
28#include <KMessageBox>
29#include <KStandardAction>
30
31#include <QCheckBox>
32#include <QComboBox>
33#include <QCompleter>
34#include <QElapsedTimer>
35#include <QKeyEvent>
36#include <QMenu>
37#include <QRegularExpression>
38#include <QShortcut>
39#include <QStringListModel>
40#include <QVBoxLayout>
41
42#include <vector>
43
44// Turn debug messages on/off here
45// #define FAST_DEBUG_ENABLE
46
47#ifdef FAST_DEBUG_ENABLE
48#define FAST_DEBUG(x) qCDebug(LOG_KTE) << x
49#else
50#define FAST_DEBUG(x)
51#endif
52
53using namespace KTextEditor;
54
55namespace
56{
57class AddMenuManager
58{
59private:
60 QList<QString> m_insertBefore;
61 QList<QString> m_insertAfter;
62 QSet<QAction *> m_actionPointers;
63 uint m_indexWalker;
64 QMenu *m_menu;
65
66public:
67 AddMenuManager(QMenu *parent, int expectedItemCount)
68 : m_insertBefore(QList<QString>(expectedItemCount))
69 , m_insertAfter(QList<QString>(expectedItemCount))
70 , m_indexWalker(0)
71 , m_menu(nullptr)
72 {
73 Q_ASSERT(parent != nullptr);
74 m_menu = parent->addMenu(i18n("Add..."));
75 if (m_menu == nullptr) {
76 return;
77 }
78 m_menu->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
79 }
80
81 void enableMenu(bool enabled)
82 {
83 if (m_menu == nullptr) {
84 return;
85 }
86 m_menu->setEnabled(enabled);
87 }
88
89 void addEntry(const QString &before,
90 const QString &after,
91 const QString &description,
92 const QString &realBefore = QString(),
93 const QString &realAfter = QString())
94 {
95 if (m_menu == nullptr) {
96 return;
97 }
98 QAction *const action = m_menu->addAction(before + after + QLatin1Char('\t') + description);
99 m_insertBefore[m_indexWalker] = QString(realBefore.isEmpty() ? before : realBefore);
100 m_insertAfter[m_indexWalker] = QString(realAfter.isEmpty() ? after : realAfter);
101 action->setData(QVariant(m_indexWalker++));
102 m_actionPointers.insert(action);
103 }
104
105 void addSeparator()
106 {
107 if (m_menu == nullptr) {
108 return;
109 }
110 m_menu->addSeparator();
111 }
112
113 void handle(QAction *action, QLineEdit *lineEdit)
114 {
115 if (!m_actionPointers.contains(action)) {
116 return;
117 }
118
119 const int cursorPos = lineEdit->cursorPosition();
120 const int index = action->data().toUInt();
121 const QString &before = m_insertBefore[index];
122 const QString &after = m_insertAfter[index];
123 lineEdit->insert(before + after);
124 lineEdit->setCursorPosition(cursorPos + before.size());
125 lineEdit->setFocus();
126 }
127};
128
129} // anon namespace
130
131KateSearchBar::KateSearchBar(bool initAsPower, KTextEditor::ViewPrivate *view, KateViewConfig *config)
132 : KateViewBarWidget(true, view)
133 , m_view(view)
134 , m_config(config)
135 , m_layout(new QVBoxLayout())
136 , m_widget(nullptr)
137 , m_incUi(nullptr)
138 , m_incInitCursor(view->cursorPosition())
139 , m_powerUi(nullptr)
140 , highlightMatchAttribute(new Attribute())
141 , highlightReplacementAttribute(new Attribute())
142 , m_incHighlightAll(false)
143 , m_incFromCursor(true)
144 , m_incMatchCase(false)
145 , m_powerMatchCase(true)
146 , m_powerFromCursor(false)
147 , m_powerHighlightAll(false)
148 , m_powerMode(0)
149{
150 connect(view, &KTextEditor::View::cursorPositionChanged, this, &KateSearchBar::updateIncInitCursor);
151 connect(view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly);
152 connect(this, &KateSearchBar::findOrReplaceAllFinished, this, &KateSearchBar::endFindOrReplaceAll);
153
154 auto setSelectionChangedByUndoRedo = [this]() {
155 m_selectionChangedByUndoRedo = true;
156 };
157 auto unsetSelectionChangedByUndoRedo = [this]() {
158 m_selectionChangedByUndoRedo = false;
159 };
160 KateUndoManager *docUndoManager = view->doc()->undoManager();
161 connect(docUndoManager, &KateUndoManager::undoStart, this, setSelectionChangedByUndoRedo);
162 connect(docUndoManager, &KateUndoManager::undoEnd, this, unsetSelectionChangedByUndoRedo);
163 connect(docUndoManager, &KateUndoManager::redoStart, this, setSelectionChangedByUndoRedo);
164 connect(docUndoManager, &KateUndoManager::redoEnd, this, unsetSelectionChangedByUndoRedo);
165
166 // When document is reloaded, disable selection-only search so that the search won't be stuck after the first search
167 connect(view->doc(), &KTextEditor::Document::reloaded, this, [this]() {
168 setSelectionOnly(false);
169 });
170
171 // init match attribute
172 Attribute::Ptr mouseInAttribute(new Attribute());
173 mouseInAttribute->setFontBold(true);
174 highlightMatchAttribute->setDynamicAttribute(Attribute::ActivateMouseIn, mouseInAttribute);
175
176 Attribute::Ptr caretInAttribute(new Attribute());
177 caretInAttribute->setFontItalic(true);
178 highlightMatchAttribute->setDynamicAttribute(Attribute::ActivateCaretIn, caretInAttribute);
179
180 updateHighlightColors();
181
182 // Modify parent
183 QWidget *const widget = centralWidget();
184 widget->setLayout(m_layout);
185 m_layout->setContentsMargins(0, 0, 0, 0);
186
187 // allow to have small size, for e.g. Kile
188 setMinimumWidth(100);
189
190 // Copy global to local config backup
191 const auto searchFlags = m_config->searchFlags();
192 m_incHighlightAll = (searchFlags & KateViewConfig::IncHighlightAll) != 0;
193 m_incFromCursor = (searchFlags & KateViewConfig::IncFromCursor) != 0;
194 m_incMatchCase = (searchFlags & KateViewConfig::IncMatchCase) != 0;
195 m_powerMatchCase = (searchFlags & KateViewConfig::PowerMatchCase) != 0;
196 m_powerFromCursor = (searchFlags & KateViewConfig::PowerFromCursor) != 0;
197 m_powerHighlightAll = (searchFlags & KateViewConfig::PowerHighlightAll) != 0;
198 m_powerMode = ((searchFlags & KateViewConfig::PowerModeRegularExpression) != 0)
199 ? MODE_REGEX
200 : (((searchFlags & KateViewConfig::PowerModeEscapeSequences) != 0)
201 ? MODE_ESCAPE_SEQUENCES
202 : (((searchFlags & KateViewConfig::PowerModeWholeWords) != 0) ? MODE_WHOLE_WORDS : MODE_PLAIN_TEXT));
203
204 // Load one of either dialogs
205 if (initAsPower) {
206 enterPowerMode();
207 } else {
208 enterIncrementalMode();
209 }
210
211 updateSelectionOnly();
212}
213
214KateSearchBar::~KateSearchBar()
215{
216 if (!m_cancelFindOrReplace) {
217 // Finish/Cancel the still running job to avoid a crash
218 endFindOrReplaceAll();
219 }
220
221 clearHighlights();
222 delete m_layout;
223 delete m_widget;
224
225 delete m_incUi;
226 delete m_powerUi;
227 if (m_workingRange) {
228 delete m_workingRange;
229 }
230}
231
232void KateSearchBar::closed()
233{
234 // remove search from the view bar, because it vertically bloats up the
235 // stacked layout in KateViewBar.
236 if (viewBar()) {
237 viewBar()->removeBarWidget(this);
238 }
239
240 clearHighlights();
241 m_replacement.clear();
242 m_unfinishedSearchText.clear();
243}
244
245void KateSearchBar::setReplacementPattern(const QString &replacementPattern)
246{
247 Q_ASSERT(isPower());
248
249 if (this->replacementPattern() == replacementPattern) {
250 return;
251 }
252
253 m_powerUi->replacement->setEditText(replacementPattern);
254}
255
256QString KateSearchBar::replacementPattern() const
257{
258 Q_ASSERT(isPower());
259
260 return m_powerUi->replacement->currentText();
261}
262
263void KateSearchBar::setSearchMode(KateSearchBar::SearchMode mode)
264{
265 Q_ASSERT(isPower());
266
267 m_powerUi->searchMode->setCurrentIndex(mode);
268}
269
270void KateSearchBar::findNext()
271{
272 const bool found = find();
273
274 if (found) {
275 QComboBox *combo = m_powerUi != nullptr ? m_powerUi->pattern : m_incUi->pattern;
276
277 // Add to search history
278 addCurrentTextToHistory(combo);
279 }
280}
281
282void KateSearchBar::findPrevious()
283{
284 const bool found = find(SearchBackward);
285
286 if (found) {
287 QComboBox *combo = m_powerUi != nullptr ? m_powerUi->pattern : m_incUi->pattern;
288
289 // Add to search history
290 addCurrentTextToHistory(combo);
291 }
292}
293
294void KateSearchBar::showResultMessage()
295{
296 QString text;
297
298 if (m_replaceMode) {
299 text = i18ncp("short translation", "1 replacement made", "%1 replacements made", m_matchCounter);
300 } else {
301 text = i18ncp("short translation", "1 match found", "%1 matches found", m_matchCounter);
302 }
303
304 if (m_infoMessage) {
305 m_infoMessage->setText(text);
306 } else {
307 m_infoMessage = new KTextEditor::Message(text, KTextEditor::Message::Positive);
308 m_infoMessage->setPosition(KTextEditor::Message::BottomInView);
309 m_infoMessage->setAutoHide(3000); // 3 seconds
310 m_infoMessage->setView(m_view);
311 m_view->doc()->postMessage(m_infoMessage);
312 }
313}
314
315void KateSearchBar::highlightMatch(Range range)
316{
317 KTextEditor::MovingRange *const highlight = m_view->doc()->newMovingRange(range, Kate::TextRange::DoNotExpand);
318 highlight->setView(m_view); // show only in this view
319 highlight->setAttributeOnlyForViews(true);
320 // use z depth defined in moving ranges interface
321 highlight->setZDepth(-10000.0);
322 highlight->setAttribute(highlightMatchAttribute);
323 m_hlRanges.append(highlight);
324}
325
326void KateSearchBar::highlightReplacement(Range range)
327{
328 KTextEditor::MovingRange *const highlight = m_view->doc()->newMovingRange(range, Kate::TextRange::DoNotExpand);
329 highlight->setView(m_view); // show only in this view
330 highlight->setAttributeOnlyForViews(true);
331 // use z depth defined in moving ranges interface
332 highlight->setZDepth(-10000.0);
333 highlight->setAttribute(highlightReplacementAttribute);
334 m_hlRanges.append(highlight);
335}
336
337void KateSearchBar::indicateMatch(MatchResult matchResult)
338{
339 QLineEdit *const lineEdit = isPower() ? m_powerUi->pattern->lineEdit() : m_incUi->pattern->lineEdit();
340 QPalette background(lineEdit->palette());
341
342 switch (matchResult) {
343 case MatchFound: // FALLTHROUGH
344 case MatchWrappedForward:
345 case MatchWrappedBackward:
346 // Green background for line edit
348 break;
349 case MatchMismatch:
350 // Red background for line edit
352 break;
353 case MatchNothing:
354 // Reset background of line edit
355 background = QPalette();
356 break;
357 case MatchNeutral:
359 break;
360 }
361
362 // Update status label
363 if (m_incUi != nullptr) {
364 QPalette foreground(m_incUi->status->palette());
365 switch (matchResult) {
366 case MatchFound: // FALLTHROUGH
367 case MatchNothing:
369 m_incUi->status->clear();
370 break;
371 case MatchWrappedForward:
372 case MatchWrappedBackward:
374 if (matchResult == MatchWrappedBackward) {
375 m_incUi->status->setText(i18n("Reached top, continued from bottom"));
376 } else {
377 m_incUi->status->setText(i18n("Reached bottom, continued from top"));
378 }
379 break;
380 case MatchMismatch:
382 m_incUi->status->setText(i18n("Not found"));
383 break;
384 case MatchNeutral:
385 /* do nothing */
386 break;
387 }
388 m_incUi->status->setPalette(foreground);
389 }
390
391 lineEdit->setPalette(background);
392}
393
394/*static*/ void KateSearchBar::selectRange(KTextEditor::ViewPrivate *view, KTextEditor::Range range)
395{
396 view->setCursorPositionInternal(range.end());
397 view->setSelection(range);
398}
399
400void KateSearchBar::selectRange2(KTextEditor::Range range)
401{
402 disconnect(m_view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly);
403 selectRange(m_view, range);
404 connect(m_view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly);
405}
406
407void KateSearchBar::onIncPatternChanged(const QString &pattern)
408{
409 if (!m_incUi) {
410 return;
411 }
412
413 // clear prior highlightings (deletes info message if present)
414 clearHighlights();
415
416 m_incUi->next->setDisabled(pattern.isEmpty());
417 m_incUi->prev->setDisabled(pattern.isEmpty());
418
419 KateMatch match(m_view->doc(), searchOptions());
420
421 if (!pattern.isEmpty()) {
422 // Find, first try
423 const Range inputRange = KTextEditor::Range(m_incInitCursor, m_view->document()->documentEnd());
424 match.searchText(inputRange, pattern);
425 }
426
427 const bool wrap = !match.isValid() && !pattern.isEmpty();
428
429 if (wrap) {
430 // Find, second try
431 const KTextEditor::Range inputRange = m_view->document()->documentRange();
432 match.searchText(inputRange, pattern);
433 }
434
435 const MatchResult matchResult = match.isValid() ? (wrap ? MatchWrappedForward : MatchFound) : pattern.isEmpty() ? MatchNothing : MatchMismatch;
436
437 const Range selectionRange = pattern.isEmpty() ? Range(m_incInitCursor, m_incInitCursor) : match.isValid() ? match.range() : Range::invalid();
438
439 // don't update m_incInitCursor when we move the cursor
440 disconnect(m_view, &KTextEditor::View::cursorPositionChanged, this, &KateSearchBar::updateIncInitCursor);
441 selectRange2(selectionRange);
442 connect(m_view, &KTextEditor::View::cursorPositionChanged, this, &KateSearchBar::updateIncInitCursor);
443
444 indicateMatch(matchResult);
445}
446
447void KateSearchBar::setMatchCase(bool matchCase)
448{
449 if (this->matchCase() == matchCase) {
450 return;
451 }
452
453 if (isPower()) {
454 m_powerUi->matchCase->setChecked(matchCase);
455 } else {
456 m_incUi->matchCase->setChecked(matchCase);
457 }
458}
459
460void KateSearchBar::onMatchCaseToggled(bool /*matchCase*/)
461{
462 sendConfig();
463
464 if (m_incUi != nullptr) {
465 // Re-search with new settings
466 const QString pattern = m_incUi->pattern->currentText();
467 onIncPatternChanged(pattern);
468 } else {
469 indicateMatch(MatchNothing);
470 }
471}
472
473bool KateSearchBar::matchCase() const
474{
475 return isPower() ? m_powerUi->matchCase->isChecked() : m_incUi->matchCase->isChecked();
476}
477
478void KateSearchBar::onReturnPressed()
479{
481 const bool shiftDown = (modifiers & Qt::ShiftModifier) != 0;
482 const bool controlDown = (modifiers & Qt::ControlModifier) != 0;
483
484 if (shiftDown) {
485 // Shift down, search backwards
486 findPrevious();
487 } else {
488 // Shift up, search forwards
489 findNext();
490 }
491
492 if (controlDown) {
493 Q_EMIT hideMe();
494 }
495}
496
497bool KateSearchBar::findOrReplace(SearchDirection searchDirection, const QString *replacement)
498{
499 // What to find?
500 if (searchPattern().isEmpty()) {
501 return false; // == Pattern error
502 }
503
504 // don't let selectionChanged signal mess around in this routine
505 disconnect(m_view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly);
506
507 // clear previous highlights if there are any
508 clearHighlights();
509
510 const SearchOptions enabledOptions = searchOptions(searchDirection);
511
512 // Where to find?
513 Range inputRange;
514 const Range selection = m_view->selection() ? m_view->selectionRange() : Range::invalid();
515 if (selection.isValid()) {
516 if (selectionOnly()) {
517 if (m_workingRange == nullptr) {
518 m_workingRange =
520 }
521 if (!m_workingRange->toRange().isValid()) {
522 // First match in selection
523 inputRange = selection;
524 // Remember selection for succeeding selection-only searches
525 // Elsewhere, make sure m_workingRange is invalidated when selection/search range changes
526 m_workingRange->setRange(selection);
527 } else {
528 // The selection wasn't changed/updated by user, so we use the previous selection
529 // We use the selection's start/end so that the search can move forward/backward
530 if (searchDirection == SearchBackward) {
531 inputRange.setRange(m_workingRange->start(), selection.end());
532 } else {
533 inputRange.setRange(selection.start(), m_workingRange->end());
534 }
535 }
536 } else {
537 // Next match after/before selection if a match was selected before
538 if (searchDirection == SearchForward) {
539 inputRange.setRange(selection.start(), m_view->document()->documentEnd());
540 } else {
541 inputRange.setRange(Cursor(0, 0), selection.end());
542 }
543
544 // Discard selection/search range previously remembered
545 if (m_workingRange) {
546 delete m_workingRange;
547 m_workingRange = nullptr;
548 }
549 }
550 } else {
551 // No selection
552 setSelectionOnly(false);
553 const Cursor cursorPos = m_view->cursorPosition();
554 if (searchDirection == SearchForward) {
555 inputRange.setRange(cursorPos, m_view->document()->documentEnd());
556 } else {
557 inputRange.setRange(Cursor(0, 0), cursorPos);
558 }
559 }
560 FAST_DEBUG("Search range is" << inputRange);
561
562 KateMatch match(m_view->doc(), enabledOptions);
563 Range afterReplace = Range::invalid();
564
565 // Find, first try
566 match.searchText(inputRange, searchPattern());
567 if (match.isValid()) {
568 if (match.range() == selection) {
569 // Same match again
570 if (replacement != nullptr) {
571 // Selection is match -> replace
572 KTextEditor::MovingRange *smartInputRange =
573 m_view->doc()->newMovingRange(inputRange, KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight);
574 afterReplace = match.replace(*replacement, m_view->blockSelection());
575 inputRange = *smartInputRange;
576 delete smartInputRange;
577 }
578
579 // Find, second try after old selection
580 if (searchDirection == SearchForward) {
581 const Cursor start = (replacement != nullptr) ? afterReplace.end() : selection.end();
582 inputRange.setRange(start, inputRange.end());
583 } else {
584 const Cursor end = (replacement != nullptr) ? afterReplace.start() : selection.start();
585 inputRange.setRange(inputRange.start(), end);
586 }
587
588 match.searchText(inputRange, searchPattern());
589
590 } else if (match.isEmpty() && match.range().end() == m_view->cursorPosition()) {
591 // valid zero-length match, e.g.: '^', '$', '\b'
592 // advance the range to avoid looping
593 KTextEditor::DocumentCursor zeroLenMatch(m_view->doc(), match.range().end());
594
595 if (searchDirection == SearchForward) {
596 zeroLenMatch.move(1);
597 inputRange.setRange(zeroLenMatch.toCursor(), inputRange.end());
598 } else { // SearchBackward
599 zeroLenMatch.move(-1);
600 inputRange.setRange(inputRange.start(), zeroLenMatch.toCursor());
601 }
602
603 match.searchText(inputRange, searchPattern());
604 }
605 }
606
607 bool askWrap = !match.isValid() && (!afterReplace.isValid() || !selectionOnly());
608 bool wrap = false;
609 if (askWrap) {
610 askWrap = false;
611 wrap = true;
612 }
613
614 if (askWrap) {
615 QString question =
616 searchDirection == SearchForward ? i18n("Bottom of file reached. Continue from top?") : i18n("Top of file reached. Continue from bottom?");
617 wrap = (KMessageBox::questionTwoActions(nullptr,
618 question,
619 i18n("Continue search?"),
622 QStringLiteral("DoNotShowAgainContinueSearchDialog"))
624 }
625 if (wrap) {
626 m_view->showSearchWrappedHint(searchDirection == SearchBackward);
627 if (selectionOnly() && m_workingRange != nullptr && m_workingRange->toRange().isValid()) {
628 inputRange = m_workingRange->toRange();
629 } else {
630 inputRange = m_view->document()->documentRange();
631 }
632 match.searchText(inputRange, searchPattern());
633 }
634
635 if (match.isValid()) {
636 selectRange2(match.range());
637 }
638
639 const MatchResult matchResult = !match.isValid() ? MatchMismatch
640 : !wrap ? MatchFound
641 : searchDirection == SearchForward ? MatchWrappedForward
642 : MatchWrappedBackward;
643 indicateMatch(matchResult);
644
645 // highlight replacements if applicable
646 if (afterReplace.isValid()) {
647 highlightReplacement(afterReplace);
648 }
649
650 // restore connection
651 connect(m_view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly);
652
653 return true; // == No pattern error
654}
655
656void KateSearchBar::findAll()
657{
658 // clear highlightings of prior search&replace action
659 clearHighlights();
660
661 Range inputRange = (m_view->selection() && selectionOnly()) ? m_view->selectionRange() : m_view->document()->documentRange();
662
663 beginFindAll(inputRange);
664}
665
666void KateSearchBar::onPowerPatternChanged(const QString & /*pattern*/)
667{
668 givePatternFeedback();
669 indicateMatch(MatchNothing);
670}
671
672bool KateSearchBar::isPatternValid() const
673{
674 if (searchPattern().isEmpty()) {
675 return false;
676 }
677
678 return searchOptions().testFlag(WholeWords) ? searchPattern().trimmed() == searchPattern()
679 : searchOptions().testFlag(Regex) ? QRegularExpression(searchPattern(), QRegularExpression::UseUnicodePropertiesOption).isValid()
680 : true;
681}
682
683void KateSearchBar::givePatternFeedback()
684{
685 // Enable/disable next/prev and replace next/all
686 m_powerUi->findNext->setEnabled(isPatternValid());
687 m_powerUi->findPrev->setEnabled(isPatternValid());
688 m_powerUi->replaceNext->setEnabled(isPatternValid());
689 m_powerUi->replaceAll->setEnabled(isPatternValid());
690 m_powerUi->findAll->setEnabled(isPatternValid());
691}
692
693void KateSearchBar::addCurrentTextToHistory(QComboBox *combo)
694{
695 const QString text = combo->currentText();
696 const int index = combo->findText(text);
697
698 if (index > 0) {
699 combo->removeItem(index);
700 }
701 if (index != 0) {
702 combo->insertItem(0, text);
703 combo->setCurrentIndex(0);
704 }
705
706 // sync to application config
708}
709
710void KateSearchBar::backupConfig(bool ofPower)
711{
712 if (ofPower) {
713 m_powerMatchCase = m_powerUi->matchCase->isChecked();
714 m_powerMode = m_powerUi->searchMode->currentIndex();
715 } else {
716 m_incMatchCase = m_incUi->matchCase->isChecked();
717 }
718}
719
720void KateSearchBar::sendConfig()
721{
722 const auto pastFlags = m_config->searchFlags();
723 auto futureFlags = pastFlags;
724
725 if (m_powerUi != nullptr) {
726 const bool OF_POWER = true;
727 backupConfig(OF_POWER);
728
729 // Update power search flags only
730 const auto incFlagsOnly = pastFlags & (KateViewConfig::IncHighlightAll | KateViewConfig::IncFromCursor | KateViewConfig::IncMatchCase);
731
732 futureFlags = incFlagsOnly | (m_powerMatchCase ? KateViewConfig::PowerMatchCase : 0) | (m_powerFromCursor ? KateViewConfig::PowerFromCursor : 0)
733 | (m_powerHighlightAll ? KateViewConfig::PowerHighlightAll : 0)
734 | ((m_powerMode == MODE_REGEX)
735 ? KateViewConfig::PowerModeRegularExpression
736 : ((m_powerMode == MODE_ESCAPE_SEQUENCES)
737 ? KateViewConfig::PowerModeEscapeSequences
738 : ((m_powerMode == MODE_WHOLE_WORDS) ? KateViewConfig::PowerModeWholeWords : KateViewConfig::PowerModePlainText)));
739
740 } else if (m_incUi != nullptr) {
741 const bool OF_INCREMENTAL = false;
742 backupConfig(OF_INCREMENTAL);
743
744 // Update incremental search flags only
745 const auto powerFlagsOnly = pastFlags
746 & (KateViewConfig::PowerMatchCase | KateViewConfig::PowerFromCursor | KateViewConfig::PowerHighlightAll | KateViewConfig::PowerModeRegularExpression
747 | KateViewConfig::PowerModeEscapeSequences | KateViewConfig::PowerModeWholeWords | KateViewConfig::PowerModePlainText);
748
749 futureFlags = powerFlagsOnly | (m_incHighlightAll ? KateViewConfig::IncHighlightAll : 0) | (m_incFromCursor ? KateViewConfig::IncFromCursor : 0)
750 | (m_incMatchCase ? KateViewConfig::IncMatchCase : 0);
751 }
752
753 // Adjust global config
754 m_config->setSearchFlags(futureFlags);
755}
756
757void KateSearchBar::replaceNext()
758{
759 const QString replacement = m_powerUi->replacement->currentText();
760
761 if (findOrReplace(SearchForward, &replacement)) {
762 // Never merge replace actions with other replace actions/user actions
763 m_view->doc()->undoManager()->undoSafePoint();
764
765 // Add to search history
766 addCurrentTextToHistory(m_powerUi->pattern);
767
768 // Add to replace history
769 addCurrentTextToHistory(m_powerUi->replacement);
770 }
771}
772
773// replacement == NULL --> Only highlight all matches
774// replacement != NULL --> Replace and highlight all matches
775void KateSearchBar::beginFindOrReplaceAll(Range inputRange, const QString &replacement, bool replaceMode /* = true*/)
776{
777 // don't let selectionChanged signal mess around in this routine
778 disconnect(m_view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly);
779 // Cancel job when user close the document to avoid crash
780 connect(m_view->doc(), &KTextEditor::Document::aboutToClose, this, &KateSearchBar::endFindOrReplaceAll);
781
782 if (m_powerUi) {
783 // Offer Cancel button and disable not useful buttons
784 m_powerUi->searchCancelStacked->setCurrentIndex(m_powerUi->searchCancelStacked->indexOf(m_powerUi->cancelPage));
785 m_powerUi->findNext->setEnabled(false);
786 m_powerUi->findPrev->setEnabled(false);
787 m_powerUi->replaceNext->setEnabled(false);
788 }
789
790 m_highlightRanges.clear();
791 m_inputRange = inputRange;
792 m_workingRange = m_view->doc()->newMovingRange(m_inputRange);
793 m_replacement = replacement;
794 m_replaceMode = replaceMode;
795 m_matchCounter = 0;
796 m_cancelFindOrReplace = false; // Ensure we have a GO!
797
798 findOrReplaceAll();
799}
800
801void KateSearchBar::findOrReplaceAll()
802{
803 const SearchOptions enabledOptions = searchOptions(SearchForward);
804
805 // we highlight all ranges of a replace, up to some hard limit
806 // e.g. if you replace 100000 things, rendering will break down otherwise ;=)
807 const int maxHighlightings = 65536;
808
809 // reuse match object to avoid massive moving range creation
810 KateMatch match(m_view->doc(), enabledOptions);
811
812 // Ignore block mode if selectionOnly option is disabled (see bug 456367)
813 bool block = m_view->selection() && m_view->blockSelection() && selectionOnly();
814
815 int line = m_inputRange.start().line();
816
817 bool timeOut = false;
818 bool done = false;
819
820 // This variable holds the number of lines that we have searched
821 // When it reaches 50K, we break the loop to allow event processing
822 int numLinesSearched = 0;
823 // Use a simple range in the loop to avoid needless work
824 KTextEditor::Range workingRangeCopy = m_workingRange->toRange();
825 do {
826 if (block) {
827 delete m_workingRange; // Never forget that!
828 m_workingRange = m_view->doc()->newMovingRange(m_view->doc()->rangeOnLine(m_inputRange, line));
829 workingRangeCopy = m_workingRange->toRange();
830 }
831
832 do {
833 int currentSearchLine = workingRangeCopy.start().line();
834 match.searchText(workingRangeCopy, searchPattern());
835 if (!match.isValid()) {
836 done = true;
837 break;
838 }
839 bool const originalMatchEmpty = match.isEmpty();
840
841 // Work with the match
842 Range lastRange;
843 if (m_replaceMode) {
844 if (m_matchCounter == 0) {
845 static_cast<KTextEditor::DocumentPrivate *>(m_view->document())->editStart();
846 }
847
848 // Replace
849 lastRange = match.replace(m_replacement, false, ++m_matchCounter);
850 // update working range as text must have changed now
851 workingRangeCopy = m_workingRange->toRange();
852 } else {
853 lastRange = match.range();
854 ++m_matchCounter;
855 }
856
857 // remember ranges if limit not reached
858 if (m_matchCounter < maxHighlightings) {
859 m_highlightRanges.push_back(lastRange);
860 } else {
861 m_highlightRanges.clear();
862 // TODO Info user that highlighting is disabled
863 }
864
865 // Continue after match
866 if (lastRange.end() >= workingRangeCopy.end()) {
867 done = true;
868 break;
869 }
870
871 KTextEditor::DocumentCursor workingStart(m_view->doc(), lastRange.end());
872
873 if (originalMatchEmpty) {
874 // Can happen for regex patterns with zero-length matches, e.g. ^, $, \b
875 // If we don't advance here we will loop forever...
876 workingStart.move(1);
877 }
878 workingRangeCopy.setRange(workingStart.toCursor(), workingRangeCopy.end());
879
880 // Are we done?
881 if (!workingRangeCopy.isValid() || workingStart.atEndOfDocument()) {
882 done = true;
883 break;
884 }
885
886 // Check if we have searched through 50K lines and time out.
887 // We do this to allow the search operation to be cancelled
888 numLinesSearched += workingRangeCopy.start().line() - currentSearchLine;
889 timeOut = numLinesSearched >= 50000;
890
891 } while (!m_cancelFindOrReplace && !timeOut);
892
893 } while (!m_cancelFindOrReplace && !timeOut && block && ++line <= m_inputRange.end().line());
894
895 // update m_workingRange
896 m_workingRange->setRange(workingRangeCopy);
897
898 if (done || m_cancelFindOrReplace) {
899 Q_EMIT findOrReplaceAllFinished();
900 } else if (timeOut) {
901 QTimer::singleShot(0, this, &KateSearchBar::findOrReplaceAll);
902 }
903
904 showResultMessage();
905}
906
907void KateSearchBar::endFindOrReplaceAll()
908{
909 // Don't forget to remove our "crash protector"
910 disconnect(m_view->doc(), &KTextEditor::Document::aboutToClose, this, &KateSearchBar::endFindOrReplaceAll);
911
912 // After last match
913 if (m_matchCounter > 0) {
914 if (m_replaceMode) {
915 static_cast<KTextEditor::DocumentPrivate *>(m_view->document())->editEnd();
916 }
917 }
918
919 // Add ScrollBarMarks
920 if (!m_highlightRanges.empty()) {
921 m_view->document()->setMarkDescription(KTextEditor::Document::SearchMatch, i18n("SearchHighLight"));
922 m_view->document()->setMarkIcon(KTextEditor::Document::SearchMatch, QIcon());
923 for (const Range &r : m_highlightRanges) {
924 m_view->document()->addMark(r.start().line(), KTextEditor::Document::SearchMatch);
925 }
926 }
927
928 // Add highlights
929 if (m_replaceMode) {
930 for (const Range &r : std::as_const(m_highlightRanges)) {
931 highlightReplacement(r);
932 }
933 // Never merge replace actions with other replace actions/user actions
934 m_view->doc()->undoManager()->undoSafePoint();
935
936 } else {
937 for (const Range &r : std::as_const(m_highlightRanges)) {
938 highlightMatch(r);
939 }
940 // indicateMatch(m_matchCounter > 0 ? MatchFound : MatchMismatch); TODO
941 }
942
943 // Clean-Up the still hold MovingRange
944 delete m_workingRange;
945 m_workingRange = nullptr; // m_workingRange is also used elsewhere so we signify that it is now "unused"
946
947 // restore connection
948 connect(m_view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly);
949
950 if (m_powerUi) {
951 // Offer Find and Replace buttons and enable again useful buttons
952 m_powerUi->searchCancelStacked->setCurrentIndex(m_powerUi->searchCancelStacked->indexOf(m_powerUi->searchPage));
953 m_powerUi->findNext->setEnabled(true);
954 m_powerUi->findPrev->setEnabled(true);
955 m_powerUi->replaceNext->setEnabled(true);
956
957 // Add to search history
958 addCurrentTextToHistory(m_powerUi->pattern);
959
960 // Add to replace history
961 addCurrentTextToHistory(m_powerUi->replacement);
962 }
963
964 m_cancelFindOrReplace = true; // Indicate we are not running
965}
966
967void KateSearchBar::replaceAll()
968{
969 // clear prior highlightings (deletes info message if present)
970 clearHighlights();
971
972 // What to find/replace?
973 const QString replacement = m_powerUi->replacement->currentText();
974
975 // Where to replace?
976 const bool selected = m_view->selection();
977 Range inputRange = (selected && selectionOnly()) ? m_view->selectionRange() : m_view->document()->documentRange();
978
979 beginFindOrReplaceAll(inputRange, replacement);
980}
981
982void KateSearchBar::setSearchPattern(const QString &searchPattern)
983{
984 if (searchPattern == this->searchPattern()) {
985 return;
986 }
987
988 if (isPower()) {
989 m_powerUi->pattern->setEditText(searchPattern);
990 } else {
991 m_incUi->pattern->setEditText(searchPattern);
992 }
993}
994
995QString KateSearchBar::searchPattern() const
996{
997 return (m_powerUi != nullptr) ? m_powerUi->pattern->currentText() : m_incUi->pattern->currentText();
998}
999
1000void 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
1011bool KateSearchBar::selectionOnly() const
1012{
1013 return isPower() ? m_powerUi->selectionOnly->isChecked() : false;
1014}
1015
1016KTextEditor::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
1051struct ParInfo {
1052 int openIndex;
1053 bool capturing;
1054 int captureNumber; // 1..9
1055};
1056
1057QList<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
1127void 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
1247void 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
1264void 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
1320void 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,
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
1480void 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
1618bool KateSearchBar::clearHighlights()
1619{
1620 // Remove ScrollBarMarks
1621 const QHash<int, KTextEditor::Mark *> &marks = m_view->document()->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
1642void 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
1661void 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
1675}
1676
1677bool 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
1701void 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
1723void KateSearchBar::updateIncInitCursor()
1724{
1725 if (m_incUi == nullptr) {
1726 return;
1727 }
1728
1729 // Update init cursor
1730 m_incInitCursor = m_view->cursorPosition();
1731}
1732
1733void KateSearchBar::onPowerPatternContextMenuRequest(const QPoint &pos)
1734{
1735 const bool FOR_PATTERN = true;
1736 showExtendedContextMenu(FOR_PATTERN, pos);
1737}
1738
1739void KateSearchBar::onPowerPatternContextMenuRequest()
1740{
1741 onPowerPatternContextMenuRequest(m_powerUi->pattern->mapFromGlobal(QCursor::pos()));
1742}
1743
1744void KateSearchBar::onPowerReplacmentContextMenuRequest(const QPoint &pos)
1745{
1746 const bool FOR_REPLACEMENT = false;
1747 showExtendedContextMenu(FOR_REPLACEMENT, pos);
1748}
1749
1750void KateSearchBar::onPowerReplacmentContextMenuRequest()
1751{
1752 onPowerReplacmentContextMenuRequest(m_powerUi->replacement->mapFromGlobal(QCursor::pos()));
1753}
1754
1755void KateSearchBar::onPowerCancelFindOrReplace()
1756{
1757 m_cancelFindOrReplace = true;
1758}
1759
1760bool KateSearchBar::isPower() const
1761{
1762 return m_powerUi != nullptr;
1763}
1764
1765void 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"
static void adjustForeground(QPalette &, ForegroundRole newRole=NormalText, QPalette::ColorRole color=QPalette::Text, ColorSet set=View, KSharedConfigPtr=KSharedConfigPtr())
static void adjustBackground(QPalette &, BackgroundRole newRole=NormalBackground, QPalette::ColorRole color=QPalette::Base, ColorSet set=View, KSharedConfigPtr=KSharedConfigPtr())
A class which provides customized text decorations.
Definition attribute.h:51
@ ActivateMouseIn
Activate attribute on mouse in.
Definition attribute.h:246
@ ActivateCaretIn
Activate attribute on caret in.
Definition attribute.h:248
The Cursor represents a position in a Document.
Definition cursor.h:75
constexpr int line() const noexcept
Retrieve the line on which this cursor is situated.
Definition cursor.h:174
A Cursor which is bound to a specific Document.
void reloaded(KTextEditor::Document *document)
Emitted after the current document was reloaded.
virtual KTextEditor::Range wordRangeAt(KTextEditor::Cursor cursor) const =0
Get the text range for the word located under the text position cursor.
virtual void setMarkDescription(MarkTypes mark, const QString &text)=0
Set the mark's description to text.
virtual void setMarkIcon(MarkTypes markType, const QIcon &icon)=0
Set the mark's icon to icon.
Range documentRange() const
A Range which encompasses the whole document.
Definition document.h:785
void aboutToClose(KTextEditor::Document *document)
Warn anyone listening that the current document is about to close.
virtual void addMark(int line, uint markType)=0
Add marks of type markType to line.
virtual const QHash< int, KTextEditor::Mark * > & marks()=0
Get a hash holding all marks in the document.
virtual Cursor documentEnd() const =0
End position of the document.
virtual void removeMark(int line, uint markType)=0
Remove the mark mask of type markType from line.
void saveSearchReplaceHistoryModels()
Call this function to store the history models to the application config.
static KTextEditor::EditorPrivate * self()
Kate Part Internal stuff ;)
This class holds a Message to display in Views.
Definition message.h:94
@ BottomInView
show message as view overlay in the bottom right corner.
Definition message.h:125
@ Positive
positive information message
Definition message.h:107
A range that is bound to a specific Document, and maintains its position.
virtual void setAttribute(Attribute::Ptr attribute)=0
Sets the currently active attribute for this range.
virtual const MovingCursor & start() const =0
Retrieve start cursor of this range, read-only.
virtual void setAttributeOnlyForViews(bool onlyForViews)=0
Set if this range's attribute is only visible in views, not for example prints.
virtual void setView(View *view)=0
Sets the currently active view for this range.
virtual const MovingCursor & end() const =0
Retrieve end cursor of this range, read-only.
const Range toRange() const
Convert this clever range into a dumb one.
virtual void setZDepth(qreal zDepth)=0
Set the current Z-depth of this range.
virtual void setRange(KTextEditor::Range range)=0
Set the range of this range.
@ DoNotExpand
Don't expand to encapsulate new characters in either direction. This is the default.
@ ExpandRight
Expand to encapsulate new characters to the right of the range.
@ ExpandLeft
Expand to encapsulate new characters to the left of the range.
An object representing a section of text, from one Cursor to another.
constexpr Cursor end() const noexcept
Get the end position of this range.
constexpr Cursor start() const noexcept
Get the start position of this range.
void setRange(Range range) noexcept
Set the start and end cursors to range.start() and range.end() respectively.
constexpr bool onSingleLine() const noexcept
Check whether this range is wholly contained within one line, ie.
static constexpr Range invalid() noexcept
Returns an invalid range.
constexpr bool isValid() const noexcept
Validity check.
void cursorPositionChanged(KTextEditor::View *view, KTextEditor::Cursor newPosition)
This signal is emitted whenever the view's cursor position changed.
void selectionChanged(KTextEditor::View *view)
This signal is emitted whenever the view's selection changes.
@ Default
Default settings.
Definition document.h:48
QFlags< SearchOption > SearchOptions
Stores a combination of #SearchOption values.
Definition document.h:65
KateUndoManager implements a document's history.
Q_SCRIPTABLE Q_NOREPLY void start()
QString i18n(const char *text, const TYPE &arg...)
QString i18ncp(const char *context, const char *singular, const char *plural, const TYPE &arg...)
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
KIOCORE_EXPORT QString number(KIO::filesize_t size)
ButtonCode questionTwoActions(QWidget *parent, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const QString &dontAskAgainName=QString(), Options options=Notify)
bool isValid(QStringView ifopt)
KGuiItem cont()
KGuiItem cancel()
const QList< QKeySequence > & end()
The KTextEditor namespace contains all the public API that is required to use the KTextEditor compone...
void clicked(bool checked)
void toggled(bool checked)
QVariant data() const const
void setData(const QVariant &data)
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
void setCurrentIndex(int index)
void currentIndexChanged(int index)
void editTextChanged(const QString &text)
int findText(const QString &text, Qt::MatchFlags flags) const const
void insertItem(int index, const QIcon &icon, const QString &text, const QVariant &userData)
QLineEdit * lineEdit() const const
void removeItem(int index)
QPoint pos()
Qt::KeyboardModifiers keyboardModifiers()
QIcon fromTheme(const QString &name)
void removeWidget(QWidget *widget)
QMenu * createStandardContextMenu()
void insert(const QString &newText)
void returnPressed()
void selectAll()
void setText(const QString &)
void textChanged(const QString &text)
void append(QList< T > &&value)
void clear()
qsizetype count() const const
bool empty() const const
bool isEmpty() const const
void reserve(qsizetype size)
void resize(qsizetype size)
qsizetype size() const const
QAction * addAction(const QIcon &icon, const QString &text, Functor functor, const QKeySequence &shortcut)
QAction * addMenu(QMenu *menu)
QAction * addSeparator()
QAction * exec()
void setIcon(const QIcon &icon)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
bool disconnect(const QMetaObject::Connection &connection)
virtual bool eventFilter(QObject *watched, QEvent *event)
bool contains(const QSet< T > &other) const const
iterator insert(const T &value)
void push(const T &t)
T & top()
void clear()
bool isEmpty() const const
bool isNull() const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QString number(double n, char format, int precision)
qsizetype size() const const
QString trimmed() const const
QStringView left(qsizetype length) const const
CaseInsensitive
CustomContextMenu
MouseFocusReason
typedef KeyboardModifiers
ElideLeft
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
uint toUInt(bool *ok) const const
QWidget(QWidget *parent, Qt::WindowFlags f)
void create(WId window, bool initializeWindow, bool destroyOldWindow)
void customContextMenuRequested(const QPoint &pos)
void setEnabled(bool)
virtual bool event(QEvent *event) override
QPoint mapToGlobal(const QPoint &pos) const const
void setFocus()
void setFocusProxy(QWidget *w)
void setLayout(QLayout *layout)
virtual void showEvent(QShowEvent *event)
bool isVisible() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:15:44 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.