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
30#include <QCheckBox>
31#include <QComboBox>
32#include <QCompleter>
33#include <QElapsedTimer>
34#include <QKeyEvent>
35#include <QMenu>
36#include <QRegularExpression>
37#include <QShortcut>
38#include <QStringListModel>
39#include <QVBoxLayout>
40
41#include <vector>
42
43// Turn debug messages on/off here
44// #define FAST_DEBUG_ENABLE
45
46#ifdef FAST_DEBUG_ENABLE
47#define FAST_DEBUG(x) qCDebug(LOG_KTE) << x
48#else
49#define FAST_DEBUG(x)
50#endif
51
52using namespace KTextEditor;
53
54namespace
55{
56class AddMenuManager
57{
58private:
59 QList<QString> m_insertBefore;
60 QList<QString> m_insertAfter;
61 QSet<QAction *> m_actionPointers;
62 uint m_indexWalker;
63 QMenu *m_menu;
64
65public:
66 AddMenuManager(QMenu *parent, int expectedItemCount)
67 : m_insertBefore(QList<QString>(expectedItemCount))
68 , m_insertAfter(QList<QString>(expectedItemCount))
69 , m_indexWalker(0)
70 , m_menu(nullptr)
71 {
72 Q_ASSERT(parent != nullptr);
73 m_menu = parent->addMenu(i18n("Add..."));
74 if (m_menu == nullptr) {
75 return;
76 }
77 m_menu->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
78 }
79
80 void enableMenu(bool enabled)
81 {
82 if (m_menu == nullptr) {
83 return;
84 }
85 m_menu->setEnabled(enabled);
86 }
87
88 void addEntry(const QString &before,
89 const QString &after,
90 const QString &description,
91 const QString &realBefore = QString(),
92 const QString &realAfter = QString())
93 {
94 if (m_menu == nullptr) {
95 return;
96 }
97 QAction *const action = m_menu->addAction(before + after + QLatin1Char('\t') + description);
98 m_insertBefore[m_indexWalker] = QString(realBefore.isEmpty() ? before : realBefore);
99 m_insertAfter[m_indexWalker] = QString(realAfter.isEmpty() ? after : realAfter);
100 action->setData(QVariant(m_indexWalker++));
101 m_actionPointers.insert(action);
102 }
103
104 void addSeparator()
105 {
106 if (m_menu == nullptr) {
107 return;
108 }
109 m_menu->addSeparator();
110 }
111
112 void handle(QAction *action, QLineEdit *lineEdit)
113 {
114 if (!m_actionPointers.contains(action)) {
115 return;
116 }
117
118 const int cursorPos = lineEdit->cursorPosition();
119 const int index = action->data().toUInt();
120 const QString &before = m_insertBefore[index];
121 const QString &after = m_insertAfter[index];
122 lineEdit->insert(before + after);
123 lineEdit->setCursorPosition(cursorPos + before.size());
124 lineEdit->setFocus();
125 }
126};
127
128} // anon namespace
129
130KateSearchBar::KateSearchBar(bool initAsPower, KTextEditor::ViewPrivate *view, KateViewConfig *config)
131 : KateViewBarWidget(true, view)
132 , m_view(view)
133 , m_config(config)
134 , m_layout(new QVBoxLayout())
135 , m_widget(nullptr)
136 , m_incUi(nullptr)
137 , m_incInitCursor(view->cursorPosition())
138 , m_powerUi(nullptr)
139 , highlightMatchAttribute(new Attribute())
140 , highlightReplacementAttribute(new Attribute())
141 , m_incHighlightAll(false)
142 , m_incFromCursor(true)
143 , m_incMatchCase(false)
144 , m_powerMatchCase(true)
145 , m_powerFromCursor(false)
146 , m_powerHighlightAll(false)
147 , m_powerMode(0)
148{
149 connect(view, &KTextEditor::View::cursorPositionChanged, this, &KateSearchBar::updateIncInitCursor);
150 connect(view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly);
151 connect(this, &KateSearchBar::findOrReplaceAllFinished, this, &KateSearchBar::endFindOrReplaceAll);
152
153 auto setSelectionChangedByUndoRedo = [this]() {
154 m_selectionChangedByUndoRedo = true;
155 };
156 auto unsetSelectionChangedByUndoRedo = [this]() {
157 m_selectionChangedByUndoRedo = false;
158 };
159 KateUndoManager *docUndoManager = view->doc()->undoManager();
160 connect(docUndoManager, &KateUndoManager::undoStart, this, setSelectionChangedByUndoRedo);
161 connect(docUndoManager, &KateUndoManager::undoEnd, this, unsetSelectionChangedByUndoRedo);
162 connect(docUndoManager, &KateUndoManager::redoStart, this, setSelectionChangedByUndoRedo);
163 connect(docUndoManager, &KateUndoManager::redoEnd, this, unsetSelectionChangedByUndoRedo);
164
165 // When document is reloaded, disable selection-only search so that the search won't be stuck after the first search
166 connect(view->doc(), &KTextEditor::Document::reloaded, this, [this]() {
167 setSelectionOnly(false);
168 });
169
170 // init match attribute
171 Attribute::Ptr mouseInAttribute(new Attribute());
172 mouseInAttribute->setFontBold(true);
173 highlightMatchAttribute->setDynamicAttribute(Attribute::ActivateMouseIn, mouseInAttribute);
174
175 Attribute::Ptr caretInAttribute(new Attribute());
176 caretInAttribute->setFontItalic(true);
177 highlightMatchAttribute->setDynamicAttribute(Attribute::ActivateCaretIn, caretInAttribute);
178
179 updateHighlightColors();
180
181 // Modify parent
182 QWidget *const widget = centralWidget();
183 widget->setLayout(m_layout);
184 m_layout->setContentsMargins(0, 0, 0, 0);
185
186 // allow to have small size, for e.g. Kile
187 setMinimumWidth(100);
188
189 // Copy global to local config backup
190 const auto searchFlags = m_config->searchFlags();
191 m_incHighlightAll = (searchFlags & KateViewConfig::IncHighlightAll) != 0;
192 m_incFromCursor = (searchFlags & KateViewConfig::IncFromCursor) != 0;
193 m_incMatchCase = (searchFlags & KateViewConfig::IncMatchCase) != 0;
194 m_powerMatchCase = (searchFlags & KateViewConfig::PowerMatchCase) != 0;
195 m_powerFromCursor = (searchFlags & KateViewConfig::PowerFromCursor) != 0;
196 m_powerHighlightAll = (searchFlags & KateViewConfig::PowerHighlightAll) != 0;
197 m_powerMode = ((searchFlags & KateViewConfig::PowerModeRegularExpression) != 0)
198 ? MODE_REGEX
199 : (((searchFlags & KateViewConfig::PowerModeEscapeSequences) != 0)
200 ? MODE_ESCAPE_SEQUENCES
201 : (((searchFlags & KateViewConfig::PowerModeWholeWords) != 0) ? MODE_WHOLE_WORDS : MODE_PLAIN_TEXT));
202
203 // Load one of either dialogs
204 if (initAsPower) {
205 enterPowerMode();
206 } else {
207 enterIncrementalMode();
208 }
209
210 updateSelectionOnly();
211}
212
213KateSearchBar::~KateSearchBar()
214{
215 if (!m_cancelFindOrReplace) {
216 // Finish/Cancel the still running job to avoid a crash
217 endFindOrReplaceAll();
218 }
219
220 clearHighlights();
221 delete m_layout;
222 delete m_widget;
223
224 delete m_incUi;
225 delete m_powerUi;
226 if (m_workingRange) {
227 delete m_workingRange;
228 }
229}
230
231void KateSearchBar::closed()
232{
233 // remove search from the view bar, because it vertically bloats up the
234 // stacked layout in KateViewBar.
235 if (viewBar()) {
236 viewBar()->removeBarWidget(this);
237 }
238
239 clearHighlights();
240 m_replacement.clear();
241 m_unfinishedSearchText.clear();
242}
243
244void KateSearchBar::setReplacementPattern(const QString &replacementPattern)
245{
246 Q_ASSERT(isPower());
247
248 if (this->replacementPattern() == replacementPattern) {
249 return;
250 }
251
252 m_powerUi->replacement->setEditText(replacementPattern);
253}
254
255QString KateSearchBar::replacementPattern() const
256{
257 Q_ASSERT(isPower());
258
259 return m_powerUi->replacement->currentText();
260}
261
262void KateSearchBar::setSearchMode(KateSearchBar::SearchMode mode)
263{
264 Q_ASSERT(isPower());
265
266 m_powerUi->searchMode->setCurrentIndex(mode);
267}
268
269void KateSearchBar::findNext()
270{
271 const bool found = find();
272
273 if (found) {
274 QComboBox *combo = m_powerUi != nullptr ? m_powerUi->pattern : m_incUi->pattern;
275
276 // Add to search history
277 addCurrentTextToHistory(combo);
278 }
279}
280
281void KateSearchBar::findPrevious()
282{
283 const bool found = find(SearchBackward);
284
285 if (found) {
286 QComboBox *combo = m_powerUi != nullptr ? m_powerUi->pattern : m_incUi->pattern;
287
288 // Add to search history
289 addCurrentTextToHistory(combo);
290 }
291}
292
293void KateSearchBar::showResultMessage()
294{
295 QString text;
296
297 if (m_replaceMode) {
298 text = i18ncp("short translation", "1 replacement made", "%1 replacements made", m_matchCounter);
299 } else {
300 text = i18ncp("short translation", "1 match found", "%1 matches found", m_matchCounter);
301 }
302
303 if (m_infoMessage) {
304 m_infoMessage->setText(text);
305 } else {
306 m_infoMessage = new KTextEditor::Message(text, KTextEditor::Message::Positive);
307 m_infoMessage->setPosition(KTextEditor::Message::BottomInView);
308 m_infoMessage->setAutoHide(3000); // 3 seconds
309 m_infoMessage->setView(m_view);
310 m_view->doc()->postMessage(m_infoMessage);
311 }
312}
313
314void KateSearchBar::highlightMatch(Range range)
315{
316 KTextEditor::MovingRange *const highlight = m_view->doc()->newMovingRange(range, Kate::TextRange::DoNotExpand);
317 highlight->setView(m_view); // show only in this view
318 highlight->setAttributeOnlyForViews(true);
319 // use z depth defined in moving ranges interface
320 highlight->setZDepth(-10000.0);
321 highlight->setAttribute(highlightMatchAttribute);
322 m_hlRanges.append(highlight);
323}
324
325void KateSearchBar::highlightReplacement(Range range)
326{
327 KTextEditor::MovingRange *const highlight = m_view->doc()->newMovingRange(range, Kate::TextRange::DoNotExpand);
328 highlight->setView(m_view); // show only in this view
329 highlight->setAttributeOnlyForViews(true);
330 // use z depth defined in moving ranges interface
331 highlight->setZDepth(-10000.0);
332 highlight->setAttribute(highlightReplacementAttribute);
333 m_hlRanges.append(highlight);
334}
335
336void KateSearchBar::indicateMatch(MatchResult matchResult)
337{
338 QLineEdit *const lineEdit = isPower() ? m_powerUi->pattern->lineEdit() : m_incUi->pattern->lineEdit();
339 QPalette background(lineEdit->palette());
340
341 switch (matchResult) {
342 case MatchFound: // FALLTHROUGH
343 case MatchWrappedForward:
344 case MatchWrappedBackward:
345 // Green background for line edit
347 break;
348 case MatchMismatch:
349 // Red background for line edit
351 break;
352 case MatchNothing:
353 // Reset background of line edit
354 background = QPalette();
355 break;
356 case MatchNeutral:
358 break;
359 }
360
361 // Update status label
362 if (m_incUi != nullptr) {
363 QPalette foreground(m_incUi->status->palette());
364 switch (matchResult) {
365 case MatchFound: // FALLTHROUGH
366 case MatchNothing:
368 m_incUi->status->clear();
369 break;
370 case MatchWrappedForward:
371 case MatchWrappedBackward:
373 if (matchResult == MatchWrappedBackward) {
374 m_incUi->status->setText(i18n("Reached top, continued from bottom"));
375 } else {
376 m_incUi->status->setText(i18n("Reached bottom, continued from top"));
377 }
378 break;
379 case MatchMismatch:
381 m_incUi->status->setText(i18n("Not found"));
382 break;
383 case MatchNeutral:
384 /* do nothing */
385 break;
386 }
387 m_incUi->status->setPalette(foreground);
388 }
389
390 lineEdit->setPalette(background);
391}
392
393/*static*/ void KateSearchBar::selectRange(KTextEditor::ViewPrivate *view, KTextEditor::Range range)
394{
395 view->setCursorPositionInternal(range.end());
396 view->setSelection(range);
397}
398
399void KateSearchBar::selectRange2(KTextEditor::Range range)
400{
401 disconnect(m_view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly);
402 selectRange(m_view, range);
403 connect(m_view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly);
404}
405
406void KateSearchBar::onIncPatternChanged(const QString &pattern)
407{
408 if (!m_incUi) {
409 return;
410 }
411
412 // clear prior highlightings (deletes info message if present)
413 clearHighlights();
414
415 m_incUi->next->setDisabled(pattern.isEmpty());
416 m_incUi->prev->setDisabled(pattern.isEmpty());
417
418 KateMatch match(m_view->doc(), searchOptions());
419
420 if (!pattern.isEmpty()) {
421 // Find, first try
422 const Range inputRange = KTextEditor::Range(m_incInitCursor, m_view->document()->documentEnd());
423 match.searchText(inputRange, pattern);
424 }
425
426 const bool wrap = !match.isValid() && !pattern.isEmpty();
427
428 if (wrap) {
429 // Find, second try
430 const KTextEditor::Range inputRange = m_view->document()->documentRange();
431 match.searchText(inputRange, pattern);
432 }
433
434 const MatchResult matchResult = match.isValid() ? (wrap ? MatchWrappedForward : MatchFound) : pattern.isEmpty() ? MatchNothing : MatchMismatch;
435
436 const Range selectionRange = pattern.isEmpty() ? Range(m_incInitCursor, m_incInitCursor) : match.isValid() ? match.range() : Range::invalid();
437
438 // don't update m_incInitCursor when we move the cursor
439 disconnect(m_view, &KTextEditor::View::cursorPositionChanged, this, &KateSearchBar::updateIncInitCursor);
440 selectRange2(selectionRange);
441 connect(m_view, &KTextEditor::View::cursorPositionChanged, this, &KateSearchBar::updateIncInitCursor);
442
443 indicateMatch(matchResult);
444}
445
446void KateSearchBar::setMatchCase(bool matchCase)
447{
448 if (this->matchCase() == matchCase) {
449 return;
450 }
451
452 if (isPower()) {
453 m_powerUi->matchCase->setChecked(matchCase);
454 } else {
455 m_incUi->matchCase->setChecked(matchCase);
456 }
457}
458
459void KateSearchBar::onMatchCaseToggled(bool /*matchCase*/)
460{
461 sendConfig();
462
463 if (m_incUi != nullptr) {
464 // Re-search with new settings
465 const QString pattern = m_incUi->pattern->currentText();
466 onIncPatternChanged(pattern);
467 } else {
468 indicateMatch(MatchNothing);
469 }
470}
471
472bool KateSearchBar::matchCase() const
473{
474 return isPower() ? m_powerUi->matchCase->isChecked() : m_incUi->matchCase->isChecked();
475}
476
477void KateSearchBar::onReturnPressed()
478{
480 const bool shiftDown = (modifiers & Qt::ShiftModifier) != 0;
481 const bool controlDown = (modifiers & Qt::ControlModifier) != 0;
482
483 if (shiftDown) {
484 // Shift down, search backwards
485 findPrevious();
486 } else {
487 // Shift up, search forwards
488 findNext();
489 }
490
491 if (controlDown) {
492 Q_EMIT hideMe();
493 }
494}
495
496bool KateSearchBar::findOrReplace(SearchDirection searchDirection, const QString *replacement)
497{
498 // What to find?
499 if (searchPattern().isEmpty()) {
500 return false; // == Pattern error
501 }
502
503 // don't let selectionChanged signal mess around in this routine
504 disconnect(m_view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly);
505
506 // clear previous highlights if there are any
507 clearHighlights();
508
509 const SearchOptions enabledOptions = searchOptions(searchDirection);
510
511 // Where to find?
512 Range inputRange;
513 const Range selection = m_view->selection() ? m_view->selectionRange() : Range::invalid();
514 if (selection.isValid()) {
515 if (selectionOnly()) {
516 if (m_workingRange == nullptr) {
517 m_workingRange =
519 }
520 if (!m_workingRange->toRange().isValid()) {
521 // First match in selection
522 inputRange = selection;
523 // Remember selection for succeeding selection-only searches
524 // Elsewhere, make sure m_workingRange is invalidated when selection/search range changes
525 m_workingRange->setRange(selection);
526 } else {
527 // The selection wasn't changed/updated by user, so we use the previous selection
528 // We use the selection's start/end so that the search can move forward/backward
529 if (searchDirection == SearchBackward) {
530 inputRange.setRange(m_workingRange->start(), selection.end());
531 } else {
532 inputRange.setRange(selection.start(), m_workingRange->end());
533 }
534 }
535 } else {
536 // Next match after/before selection if a match was selected before
537 if (searchDirection == SearchForward) {
538 inputRange.setRange(selection.start(), m_view->document()->documentEnd());
539 } else {
540 inputRange.setRange(Cursor(0, 0), selection.end());
541 }
542
543 // Discard selection/search range previously remembered
544 if (m_workingRange) {
545 delete m_workingRange;
546 m_workingRange = nullptr;
547 }
548 }
549 } else {
550 // No selection
551 setSelectionOnly(false);
552 const Cursor cursorPos = m_view->cursorPosition();
553 if (searchDirection == SearchForward) {
554 inputRange.setRange(cursorPos, m_view->document()->documentEnd());
555 } else {
556 inputRange.setRange(Cursor(0, 0), cursorPos);
557 }
558 }
559 FAST_DEBUG("Search range is" << inputRange);
560
561 KateMatch match(m_view->doc(), enabledOptions);
562 Range afterReplace = Range::invalid();
563
564 // Find, first try
565 match.searchText(inputRange, searchPattern());
566 if (match.isValid()) {
567 if (match.range() == selection) {
568 // Same match again
569 if (replacement != nullptr) {
570 // Selection is match -> replace
571 KTextEditor::MovingRange *smartInputRange =
573 afterReplace = match.replace(*replacement, m_view->blockSelection());
574 inputRange = *smartInputRange;
575 delete smartInputRange;
576 }
577
578 // Find, second try after old selection
579 if (searchDirection == SearchForward) {
580 const Cursor start = (replacement != nullptr) ? afterReplace.end() : selection.end();
581 inputRange.setRange(start, inputRange.end());
582 } else {
583 const Cursor end = (replacement != nullptr) ? afterReplace.start() : selection.start();
584 inputRange.setRange(inputRange.start(), end);
585 }
586
587 match.searchText(inputRange, searchPattern());
588
589 } else if (match.isEmpty() && match.range().end() == m_view->cursorPosition()) {
590 // valid zero-length match, e.g.: '^', '$', '\b'
591 // advance the range to avoid looping
592 KTextEditor::DocumentCursor zeroLenMatch(m_view->doc(), match.range().end());
593
594 if (searchDirection == SearchForward) {
595 zeroLenMatch.move(1);
596 inputRange.setRange(zeroLenMatch.toCursor(), inputRange.end());
597 } else { // SearchBackward
598 zeroLenMatch.move(-1);
599 inputRange.setRange(inputRange.start(), zeroLenMatch.toCursor());
600 }
601
602 match.searchText(inputRange, searchPattern());
603 }
604 }
605
606 bool askWrap = !match.isValid() && (!afterReplace.isValid() || !selectionOnly());
607 bool wrap = false;
608 if (askWrap) {
609 askWrap = false;
610 wrap = true;
611 }
612
613 if (askWrap) {
614 QString question =
615 searchDirection == SearchForward ? i18n("Bottom of file reached. Continue from top?") : i18n("Top of file reached. Continue from bottom?");
616 wrap = (KMessageBox::questionTwoActions(nullptr,
617 question,
618 i18n("Continue search?"),
621 QStringLiteral("DoNotShowAgainContinueSearchDialog"))
623 }
624 if (wrap) {
625 m_view->showSearchWrappedHint(searchDirection == SearchBackward);
626 if (selectionOnly() && m_workingRange != nullptr && m_workingRange->toRange().isValid()) {
627 inputRange = m_workingRange->toRange();
628 } else {
629 inputRange = m_view->document()->documentRange();
630 }
631 match.searchText(inputRange, searchPattern());
632 }
633
634 if (match.isValid()) {
635 selectRange2(match.range());
636 }
637
638 const MatchResult matchResult = !match.isValid() ? MatchMismatch
639 : !wrap ? MatchFound
640 : searchDirection == SearchForward ? MatchWrappedForward
641 : MatchWrappedBackward;
642 indicateMatch(matchResult);
643
644 // highlight replacements if applicable
645 if (afterReplace.isValid()) {
646 highlightReplacement(afterReplace);
647 }
648
649 // restore connection
650 connect(m_view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly);
651
652 return true; // == No pattern error
653}
654
655void KateSearchBar::findAll()
656{
657 // clear highlightings of prior search&replace action
658 clearHighlights();
659
660 Range inputRange = (m_view->selection() && selectionOnly()) ? m_view->selectionRange() : m_view->document()->documentRange();
661
662 beginFindAll(inputRange);
663}
664
665void KateSearchBar::onPowerPatternChanged(const QString & /*pattern*/)
666{
667 givePatternFeedback();
668 indicateMatch(MatchNothing);
669}
670
671bool KateSearchBar::isPatternValid() const
672{
673 if (searchPattern().isEmpty()) {
674 return false;
675 }
676
677 return searchOptions().testFlag(WholeWords) ? searchPattern().trimmed() == searchPattern()
678 : searchOptions().testFlag(Regex) ? QRegularExpression(searchPattern(), QRegularExpression::UseUnicodePropertiesOption).isValid()
679 : true;
680}
681
682void KateSearchBar::givePatternFeedback()
683{
684 // Enable/disable next/prev and replace next/all
685 m_powerUi->findNext->setEnabled(isPatternValid());
686 m_powerUi->findPrev->setEnabled(isPatternValid());
687 m_powerUi->replaceNext->setEnabled(isPatternValid());
688 m_powerUi->replaceAll->setEnabled(isPatternValid());
689 m_powerUi->findAll->setEnabled(isPatternValid());
690}
691
692void KateSearchBar::addCurrentTextToHistory(QComboBox *combo)
693{
694 const QString text = combo->currentText();
695 const int index = combo->findText(text);
696
697 if (index > 0) {
698 combo->removeItem(index);
699 }
700 if (index != 0) {
701 combo->insertItem(0, text);
702 combo->setCurrentIndex(0);
703 }
704
705 // sync to application config
707}
708
709void KateSearchBar::backupConfig(bool ofPower)
710{
711 if (ofPower) {
712 m_powerMatchCase = m_powerUi->matchCase->isChecked();
713 m_powerMode = m_powerUi->searchMode->currentIndex();
714 } else {
715 m_incMatchCase = m_incUi->matchCase->isChecked();
716 }
717}
718
719void KateSearchBar::sendConfig()
720{
721 const auto pastFlags = m_config->searchFlags();
722 auto futureFlags = pastFlags;
723
724 if (m_powerUi != nullptr) {
725 const bool OF_POWER = true;
726 backupConfig(OF_POWER);
727
728 // Update power search flags only
729 const auto incFlagsOnly = pastFlags & (KateViewConfig::IncHighlightAll | KateViewConfig::IncFromCursor | KateViewConfig::IncMatchCase);
730
731 futureFlags = incFlagsOnly | (m_powerMatchCase ? KateViewConfig::PowerMatchCase : 0) | (m_powerFromCursor ? KateViewConfig::PowerFromCursor : 0)
732 | (m_powerHighlightAll ? KateViewConfig::PowerHighlightAll : 0)
733 | ((m_powerMode == MODE_REGEX)
734 ? KateViewConfig::PowerModeRegularExpression
735 : ((m_powerMode == MODE_ESCAPE_SEQUENCES)
736 ? KateViewConfig::PowerModeEscapeSequences
737 : ((m_powerMode == MODE_WHOLE_WORDS) ? KateViewConfig::PowerModeWholeWords : KateViewConfig::PowerModePlainText)));
738
739 } else if (m_incUi != nullptr) {
740 const bool OF_INCREMENTAL = false;
741 backupConfig(OF_INCREMENTAL);
742
743 // Update incremental search flags only
744 const auto powerFlagsOnly = pastFlags
745 & (KateViewConfig::PowerMatchCase | KateViewConfig::PowerFromCursor | KateViewConfig::PowerHighlightAll | KateViewConfig::PowerModeRegularExpression
746 | KateViewConfig::PowerModeEscapeSequences | KateViewConfig::PowerModeWholeWords | KateViewConfig::PowerModePlainText);
747
748 futureFlags = powerFlagsOnly | (m_incHighlightAll ? KateViewConfig::IncHighlightAll : 0) | (m_incFromCursor ? KateViewConfig::IncFromCursor : 0)
749 | (m_incMatchCase ? KateViewConfig::IncMatchCase : 0);
750 }
751
752 // Adjust global config
753 m_config->setSearchFlags(futureFlags);
754}
755
756void KateSearchBar::replaceNext()
757{
758 const QString replacement = m_powerUi->replacement->currentText();
759
760 if (findOrReplace(SearchForward, &replacement)) {
761 // Never merge replace actions with other replace actions/user actions
762 m_view->doc()->undoManager()->undoSafePoint();
763
764 // Add to search history
765 addCurrentTextToHistory(m_powerUi->pattern);
766
767 // Add to replace history
768 addCurrentTextToHistory(m_powerUi->replacement);
769 }
770}
771
772// replacement == NULL --> Only highlight all matches
773// replacement != NULL --> Replace and highlight all matches
774void KateSearchBar::beginFindOrReplaceAll(Range inputRange, const QString &replacement, bool replaceMode /* = true*/)
775{
776 // don't let selectionChanged signal mess around in this routine
777 disconnect(m_view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly);
778 // Cancel job when user close the document to avoid crash
779 connect(m_view->doc(), &KTextEditor::Document::aboutToClose, this, &KateSearchBar::endFindOrReplaceAll);
780
781 if (m_powerUi) {
782 // Offer Cancel button and disable not useful buttons
783 m_powerUi->searchCancelStacked->setCurrentIndex(m_powerUi->searchCancelStacked->indexOf(m_powerUi->cancelPage));
784 m_powerUi->findNext->setEnabled(false);
785 m_powerUi->findPrev->setEnabled(false);
786 m_powerUi->replaceNext->setEnabled(false);
787 }
788
789 m_highlightRanges.clear();
790 m_inputRange = inputRange;
791 m_workingRange = m_view->doc()->newMovingRange(m_inputRange);
792 m_replacement = replacement;
793 m_replaceMode = replaceMode;
794 m_matchCounter = 0;
795 m_cancelFindOrReplace = false; // Ensure we have a GO!
796
797 findOrReplaceAll();
798}
799
800void KateSearchBar::findOrReplaceAll()
801{
802 const SearchOptions enabledOptions = searchOptions(SearchForward);
803
804 // we highlight all ranges of a replace, up to some hard limit
805 // e.g. if you replace 100000 things, rendering will break down otherwise ;=)
806 const int maxHighlightings = 65536;
807
808 // reuse match object to avoid massive moving range creation
809 KateMatch match(m_view->doc(), enabledOptions);
810
811 // Ignore block mode if selectionOnly option is disabled (see bug 456367)
812 bool block = m_view->selection() && m_view->blockSelection() && selectionOnly();
813
814 int line = m_inputRange.start().line();
815
816 bool timeOut = false;
817 bool done = false;
818
819 // This variable holds the number of lines that we have searched
820 // When it reaches 50K, we break the loop to allow event processing
821 int numLinesSearched = 0;
822 // Use a simple range in the loop to avoid needless work
823 KTextEditor::Range workingRangeCopy = m_workingRange->toRange();
824 do {
825 if (block) {
826 delete m_workingRange; // Never forget that!
827 m_workingRange = m_view->doc()->newMovingRange(m_view->doc()->rangeOnLine(m_inputRange, line));
828 workingRangeCopy = m_workingRange->toRange();
829 }
830
831 do {
832 int currentSearchLine = workingRangeCopy.start().line();
833 match.searchText(workingRangeCopy, searchPattern());
834 if (!match.isValid()) {
835 done = true;
836 break;
837 }
838 bool const originalMatchEmpty = match.isEmpty();
839
840 // Work with the match
841 Range lastRange;
842 if (m_replaceMode) {
843 if (m_matchCounter == 0) {
844 static_cast<KTextEditor::DocumentPrivate *>(m_view->document())->editStart();
845 }
846
847 // Replace
848 lastRange = match.replace(m_replacement, false, ++m_matchCounter);
849 // update working range as text must have changed now
850 workingRangeCopy = m_workingRange->toRange();
851 } else {
852 lastRange = match.range();
853 ++m_matchCounter;
854 }
855
856 // remember ranges if limit not reached
857 if (m_matchCounter < maxHighlightings) {
858 m_highlightRanges.push_back(lastRange);
859 } else {
860 m_highlightRanges.clear();
861 // TODO Info user that highlighting is disabled
862 }
863
864 // Continue after match
865 if (lastRange.end() >= workingRangeCopy.end()) {
866 done = true;
867 break;
868 }
869
870 KTextEditor::DocumentCursor workingStart(m_view->doc(), lastRange.end());
871
872 if (originalMatchEmpty) {
873 // Can happen for regex patterns with zero-length matches, e.g. ^, $, \b
874 // If we don't advance here we will loop forever...
875 workingStart.move(1);
876 }
877 workingRangeCopy.setRange(workingStart.toCursor(), workingRangeCopy.end());
878
879 // Are we done?
880 if (!workingRangeCopy.isValid() || workingStart.atEndOfDocument()) {
881 done = true;
882 break;
883 }
884
885 // Check if we have searched through 50K lines and time out.
886 // We do this to allow the search operation to be cancelled
887 numLinesSearched += workingRangeCopy.start().line() - currentSearchLine;
888 timeOut = numLinesSearched >= 50000;
889
890 } while (!m_cancelFindOrReplace && !timeOut);
891
892 } while (!m_cancelFindOrReplace && !timeOut && block && ++line <= m_inputRange.end().line());
893
894 // update m_workingRange
895 m_workingRange->setRange(workingRangeCopy);
896
897 if (done || m_cancelFindOrReplace) {
898 Q_EMIT findOrReplaceAllFinished();
899 } else if (timeOut) {
900 QTimer::singleShot(0, this, &KateSearchBar::findOrReplaceAll);
901 }
902
903 showResultMessage();
904}
905
906void KateSearchBar::endFindOrReplaceAll()
907{
908 // Don't forget to remove our "crash protector"
909 disconnect(m_view->doc(), &KTextEditor::Document::aboutToClose, this, &KateSearchBar::endFindOrReplaceAll);
910
911 // After last match
912 if (m_matchCounter > 0) {
913 if (m_replaceMode) {
914 static_cast<KTextEditor::DocumentPrivate *>(m_view->document())->editEnd();
915 }
916 }
917
918 // Add ScrollBarMarks
919 if (!m_highlightRanges.empty()) {
920 m_view->document()->setMarkDescription(KTextEditor::Document::SearchMatch, i18n("SearchHighLight"));
921 m_view->document()->setMarkIcon(KTextEditor::Document::SearchMatch, QIcon());
922 for (const Range &r : m_highlightRanges) {
923 m_view->document()->addMark(r.start().line(), KTextEditor::Document::SearchMatch);
924 }
925 }
926
927 // Add highlights
928 if (m_replaceMode) {
929 for (const Range &r : std::as_const(m_highlightRanges)) {
930 highlightReplacement(r);
931 }
932 // Never merge replace actions with other replace actions/user actions
933 m_view->doc()->undoManager()->undoSafePoint();
934
935 } else {
936 for (const Range &r : std::as_const(m_highlightRanges)) {
937 highlightMatch(r);
938 }
939 // indicateMatch(m_matchCounter > 0 ? MatchFound : MatchMismatch); TODO
940 }
941
942 // Clean-Up the still hold MovingRange
943 delete m_workingRange;
944 m_workingRange = nullptr; // m_workingRange is also used elsewhere so we signify that it is now "unused"
945
946 // restore connection
947 connect(m_view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly);
948
949 if (m_powerUi) {
950 // Offer Find and Replace buttons and enable again useful buttons
951 m_powerUi->searchCancelStacked->setCurrentIndex(m_powerUi->searchCancelStacked->indexOf(m_powerUi->searchPage));
952 m_powerUi->findNext->setEnabled(true);
953 m_powerUi->findPrev->setEnabled(true);
954 m_powerUi->replaceNext->setEnabled(true);
955
956 // Add to search history
957 addCurrentTextToHistory(m_powerUi->pattern);
958
959 // Add to replace history
960 addCurrentTextToHistory(m_powerUi->replacement);
961 }
962
963 m_cancelFindOrReplace = true; // Indicate we are not running
964}
965
966void KateSearchBar::replaceAll()
967{
968 // clear prior highlightings (deletes info message if present)
969 clearHighlights();
970
971 // What to find/replace?
972 const QString replacement = m_powerUi->replacement->currentText();
973
974 // Where to replace?
975 const bool selected = m_view->selection();
976 Range inputRange = (selected && selectionOnly()) ? m_view->selectionRange() : m_view->document()->documentRange();
977
978 beginFindOrReplaceAll(inputRange, replacement);
979}
980
981void KateSearchBar::setSearchPattern(const QString &searchPattern)
982{
983 if (searchPattern == this->searchPattern()) {
984 return;
985 }
986
987 if (isPower()) {
988 m_powerUi->pattern->setEditText(searchPattern);
989 } else {
990 m_incUi->pattern->setEditText(searchPattern);
991 }
992}
993
994QString KateSearchBar::searchPattern() const
995{
996 return (m_powerUi != nullptr) ? m_powerUi->pattern->currentText() : m_incUi->pattern->currentText();
997}
998
999void KateSearchBar::setSelectionOnly(bool selectionOnly)
1000{
1001 if (this->selectionOnly() == selectionOnly) {
1002 return;
1003 }
1004
1005 if (isPower()) {
1006 m_powerUi->selectionOnly->setChecked(selectionOnly);
1007 }
1008}
1009
1010bool KateSearchBar::selectionOnly() const
1011{
1012 return isPower() ? m_powerUi->selectionOnly->isChecked() : false;
1013}
1014
1015KTextEditor::SearchOptions KateSearchBar::searchOptions(SearchDirection searchDirection) const
1016{
1017 SearchOptions enabledOptions = KTextEditor::Default;
1018
1019 if (!matchCase()) {
1020 enabledOptions |= CaseInsensitive;
1021 }
1022
1023 if (searchDirection == SearchBackward) {
1024 enabledOptions |= Backwards;
1025 }
1026
1027 if (m_powerUi != nullptr) {
1028 switch (m_powerUi->searchMode->currentIndex()) {
1029 case MODE_WHOLE_WORDS:
1030 enabledOptions |= WholeWords;
1031 break;
1032
1033 case MODE_ESCAPE_SEQUENCES:
1034 enabledOptions |= EscapeSequences;
1035 break;
1036
1037 case MODE_REGEX:
1038 enabledOptions |= Regex;
1039 break;
1040
1041 case MODE_PLAIN_TEXT: // FALLTHROUGH
1042 default:
1043 break;
1044 }
1045 }
1046
1047 return enabledOptions;
1048}
1049
1050struct ParInfo {
1051 int openIndex;
1052 bool capturing;
1053 int captureNumber; // 1..9
1054};
1055
1056QList<QString> KateSearchBar::getCapturePatterns(const QString &pattern) const
1057{
1058 QList<QString> capturePatterns;
1059 capturePatterns.reserve(9);
1060 QStack<ParInfo> parInfos;
1061
1062 const int inputLen = pattern.length();
1063 int input = 0; // walker index
1064 bool insideClass = false;
1065 int captureCount = 0;
1066
1067 while (input < inputLen) {
1068 if (insideClass) {
1069 // Wait for closing, unescaped ']'
1070 if (pattern[input].unicode() == L']') {
1071 insideClass = false;
1072 }
1073 input++;
1074 } else {
1075 switch (pattern[input].unicode()) {
1076 case L'\\':
1077 // Skip this and any next character
1078 input += 2;
1079 break;
1080
1081 case L'(':
1082 ParInfo curInfo;
1083 curInfo.openIndex = input;
1084 curInfo.capturing = (input + 1 >= inputLen) || (pattern[input + 1].unicode() != '?');
1085 if (curInfo.capturing) {
1086 captureCount++;
1087 }
1088 curInfo.captureNumber = captureCount;
1089 parInfos.push(curInfo);
1090
1091 input++;
1092 break;
1093
1094 case L')':
1095 if (!parInfos.empty()) {
1096 ParInfo &top = parInfos.top();
1097 if (top.capturing && (top.captureNumber <= 9)) {
1098 const int start = top.openIndex + 1;
1099 const int len = input - start;
1100 if (capturePatterns.size() < top.captureNumber) {
1101 capturePatterns.resize(top.captureNumber);
1102 }
1103 capturePatterns[top.captureNumber - 1] = pattern.mid(start, len);
1104 }
1105 parInfos.pop();
1106 }
1107
1108 input++;
1109 break;
1110
1111 case L'[':
1112 input++;
1113 insideClass = true;
1114 break;
1115
1116 default:
1117 input++;
1118 break;
1119 }
1120 }
1121 }
1122
1123 return capturePatterns;
1124}
1125
1126void KateSearchBar::showExtendedContextMenu(bool forPattern, const QPoint &pos)
1127{
1128 // Make original menu
1129 QComboBox *comboBox = forPattern ? m_powerUi->pattern : m_powerUi->replacement;
1130 QMenu *const contextMenu = comboBox->lineEdit()->createStandardContextMenu();
1131
1132 if (contextMenu == nullptr) {
1133 return;
1134 }
1135
1136 bool extendMenu = false;
1137 bool regexMode = false;
1138 switch (m_powerUi->searchMode->currentIndex()) {
1139 case MODE_REGEX:
1140 regexMode = true;
1141 // FALLTHROUGH
1142
1143 case MODE_ESCAPE_SEQUENCES:
1144 extendMenu = true;
1145 break;
1146
1147 default:
1148 break;
1149 }
1150
1151 AddMenuManager addMenuManager(contextMenu, 37);
1152 if (!extendMenu) {
1153 addMenuManager.enableMenu(extendMenu);
1154 } else {
1155 // Build menu
1156 if (forPattern) {
1157 if (regexMode) {
1158 addMenuManager.addEntry(QStringLiteral("^"), QString(), i18n("Beginning of line"));
1159 addMenuManager.addEntry(QStringLiteral("$"), QString(), i18n("End of line"));
1160 addMenuManager.addSeparator();
1161 addMenuManager.addEntry(QStringLiteral("."), QString(), i18n("Match any character excluding new line (by default)"));
1162 addMenuManager.addEntry(QStringLiteral("+"), QString(), i18n("One or more occurrences"));
1163 addMenuManager.addEntry(QStringLiteral("*"), QString(), i18n("Zero or more occurrences"));
1164 addMenuManager.addEntry(QStringLiteral("?"), QString(), i18n("Zero or one occurrences"));
1165 addMenuManager.addEntry(QStringLiteral("{a"),
1166 QStringLiteral(",b}"),
1167 i18n("<a> through <b> occurrences"),
1168 QStringLiteral("{"),
1169 QStringLiteral(",}"));
1170
1171 addMenuManager.addSeparator();
1172 addMenuManager.addSeparator();
1173 addMenuManager.addEntry(QStringLiteral("("), QStringLiteral(")"), i18n("Group, capturing"));
1174 addMenuManager.addEntry(QStringLiteral("|"), QString(), i18n("Or"));
1175 addMenuManager.addEntry(QStringLiteral("["), QStringLiteral("]"), i18n("Set of characters"));
1176 addMenuManager.addEntry(QStringLiteral("[^"), QStringLiteral("]"), i18n("Negative set of characters"));
1177 addMenuManager.addSeparator();
1178 }
1179 } else {
1180 addMenuManager.addEntry(QStringLiteral("\\0"), QString(), i18n("Whole match reference"));
1181 addMenuManager.addSeparator();
1182 if (regexMode) {
1183 const QString pattern = m_powerUi->pattern->currentText();
1184 const QList<QString> capturePatterns = getCapturePatterns(pattern);
1185
1186 const int captureCount = capturePatterns.count();
1187 for (int i = 1; i <= 9; i++) {
1188 const QString number = QString::number(i);
1189 const QString &captureDetails =
1190 (i <= captureCount) ? QLatin1String(" = (") + QStringView(capturePatterns[i - 1]).left(30) + QLatin1Char(')') : QString();
1191 addMenuManager.addEntry(QLatin1String("\\") + number, QString(), i18n("Reference") + QLatin1Char(' ') + number + captureDetails);
1192 }
1193
1194 addMenuManager.addSeparator();
1195 }
1196 }
1197
1198 addMenuManager.addEntry(QStringLiteral("\\n"), QString(), i18n("Line break"));
1199 addMenuManager.addEntry(QStringLiteral("\\t"), QString(), i18n("Tab"));
1200
1201 if (forPattern && regexMode) {
1202 addMenuManager.addEntry(QStringLiteral("\\b"), QString(), i18n("Word boundary"));
1203 addMenuManager.addEntry(QStringLiteral("\\B"), QString(), i18n("Not word boundary"));
1204 addMenuManager.addEntry(QStringLiteral("\\d"), QString(), i18n("Digit"));
1205 addMenuManager.addEntry(QStringLiteral("\\D"), QString(), i18n("Non-digit"));
1206 addMenuManager.addEntry(QStringLiteral("\\s"), QString(), i18n("Whitespace (excluding line breaks)"));
1207 addMenuManager.addEntry(QStringLiteral("\\S"), QString(), i18n("Non-whitespace"));
1208 addMenuManager.addEntry(QStringLiteral("\\w"), QString(), i18n("Word character (alphanumerics plus '_')"));
1209 addMenuManager.addEntry(QStringLiteral("\\W"), QString(), i18n("Non-word character"));
1210 }
1211
1212 addMenuManager.addEntry(QStringLiteral("\\0???"), QString(), i18n("Octal character 000 to 377 (2^8-1)"), QStringLiteral("\\0"));
1213 addMenuManager.addEntry(QStringLiteral("\\x{????}"), QString(), i18n("Hex character 0000 to FFFF (2^16-1)"), QStringLiteral("\\x{....}"));
1214 addMenuManager.addEntry(QStringLiteral("\\\\"), QString(), i18n("Backslash"));
1215
1216 if (forPattern && regexMode) {
1217 addMenuManager.addSeparator();
1218 addMenuManager.addEntry(QStringLiteral("(?:E"), QStringLiteral(")"), i18n("Group, non-capturing"), QStringLiteral("(?:"));
1219 addMenuManager.addEntry(QStringLiteral("(?=E"), QStringLiteral(")"), i18n("Positive Lookahead"), QStringLiteral("(?="));
1220 addMenuManager.addEntry(QStringLiteral("(?!E"), QStringLiteral(")"), i18n("Negative lookahead"), QStringLiteral("(?!"));
1221 // variable length positive/negative lookbehind is an experimental feature in Perl 5.30
1222 // see: https://perldoc.perl.org/perlre.html
1223 // currently QRegularExpression only supports fixed-length positive/negative lookbehind (2020-03-01)
1224 addMenuManager.addEntry(QStringLiteral("(?<=E"), QStringLiteral(")"), i18n("Fixed-length positive lookbehind"), QStringLiteral("(?<="));
1225 addMenuManager.addEntry(QStringLiteral("(?<!E"), QStringLiteral(")"), i18n("Fixed-length negative lookbehind"), QStringLiteral("(?<!"));
1226 }
1227
1228 if (!forPattern) {
1229 addMenuManager.addSeparator();
1230 addMenuManager.addEntry(QStringLiteral("\\L"), QString(), i18n("Begin lowercase conversion"));
1231 addMenuManager.addEntry(QStringLiteral("\\U"), QString(), i18n("Begin uppercase conversion"));
1232 addMenuManager.addEntry(QStringLiteral("\\E"), QString(), i18n("End case conversion"));
1233 addMenuManager.addEntry(QStringLiteral("\\l"), QString(), i18n("Lowercase first character conversion"));
1234 addMenuManager.addEntry(QStringLiteral("\\u"), QString(), i18n("Uppercase first character conversion"));
1235 addMenuManager.addEntry(QStringLiteral("\\#[#..]"), QString(), i18n("Replacement counter (for Replace All)"), QStringLiteral("\\#"));
1236 }
1237 }
1238
1239 // Show menu
1240 QAction *const result = contextMenu->exec(comboBox->mapToGlobal(pos));
1241 if (result != nullptr) {
1242 addMenuManager.handle(result, comboBox->lineEdit());
1243 }
1244}
1245
1246void KateSearchBar::onPowerModeChanged(int /*index*/)
1247{
1248 if (m_powerUi->searchMode->currentIndex() == MODE_REGEX) {
1249 m_powerUi->matchCase->setChecked(true);
1250 }
1251
1252 sendConfig();
1253 indicateMatch(MatchNothing);
1254
1255 givePatternFeedback();
1256}
1257
1258// static void addSecondarySelection(KTextEditor::ViewPrivate *view, KTextEditor::Range range)
1259// {
1260// view->addSecondaryCursorWithSelection(range);
1261// }
1262
1263void KateSearchBar::nextMatchForSelection(KTextEditor::ViewPrivate *view, SearchDirection searchDirection)
1264{
1265 if (!view->selection()) {
1266 // Select current word so we can search for that
1267 const Cursor cursorPos = view->cursorPosition();
1268 KTextEditor::Range wordRange = view->document()->wordRangeAt(cursorPos);
1269 if (wordRange.isValid()) {
1270 selectRange(view, wordRange);
1271 return;
1272 }
1273 }
1274 if (view->selection()) {
1275 // We only want text of one of the selection ranges
1276 const QString pattern = view->doc()->text(view->selectionRange());
1277
1278 // How to find?
1279 SearchOptions enabledOptions(KTextEditor::Default);
1280 if (searchDirection == SearchBackward) {
1281 enabledOptions |= Backwards;
1282 }
1283
1284 // Where to find?
1285 const Range selRange = view->selectionRange();
1286 // const Range selRange = view->lastSelectionRange();
1287 Range inputRange;
1288 if (searchDirection == SearchForward) {
1289 inputRange.setRange(selRange.end(), view->doc()->documentEnd());
1290 } else {
1291 inputRange.setRange(Cursor(0, 0), selRange.start());
1292 }
1293
1294 // Find, first try
1295 KateMatch match(view->doc(), enabledOptions);
1296 match.searchText(inputRange, pattern);
1297
1298 if (match.isValid()) {
1299 selectRange(view, match.range());
1300 // addSecondarySelection(view, match.range());
1301 } else {
1302 // Find, second try
1303 m_view->showSearchWrappedHint(searchDirection == SearchBackward);
1304 if (searchDirection == SearchForward) {
1305 inputRange.setRange(Cursor(0, 0), selRange.start());
1306 } else {
1307 inputRange.setRange(selRange.end(), view->doc()->documentEnd());
1308 }
1309 KateMatch match2(view->doc(), enabledOptions);
1310 match2.searchText(inputRange, pattern);
1311 if (match2.isValid()) {
1312 selectRange(view, match2.range());
1313 // addSecondarySelection(view, match2.range());
1314 }
1315 }
1316 }
1317}
1318
1319void KateSearchBar::enterPowerMode()
1320{
1321 QString initialPattern;
1322 bool selectionOnly = false;
1323
1324 // Guess settings from context: init pattern with current selection
1325 const bool selected = m_view->selection();
1326 if (selected) {
1327 const Range &selection = m_view->selectionRange();
1328 if (selection.onSingleLine()) {
1329 // ... with current selection
1330 initialPattern = m_view->selectionText();
1331 } else {
1332 // Enable selection only
1333 selectionOnly = true;
1334 }
1335 }
1336
1337 // If there's no new selection, we'll use the existing pattern
1338 if (initialPattern.isNull()) {
1339 // Coming from power search?
1340 const bool fromReplace = (m_powerUi != nullptr) && (m_widget->isVisible());
1341 if (fromReplace) {
1342 QLineEdit *const patternLineEdit = m_powerUi->pattern->lineEdit();
1343 Q_ASSERT(patternLineEdit != nullptr);
1344 patternLineEdit->selectAll();
1345 m_powerUi->pattern->setFocus(Qt::MouseFocusReason);
1346 return;
1347 }
1348
1349 // Coming from incremental search?
1350 const bool fromIncremental = (m_incUi != nullptr) && (m_widget->isVisible());
1351 if (fromIncremental) {
1352 initialPattern = m_incUi->pattern->currentText();
1353 } else {
1354 // Search bar probably newly opened. Reset initial replacement text to empty
1355 m_replacement.clear();
1356 }
1357 }
1358
1359 // Create dialog
1360 const bool create = (m_powerUi == nullptr);
1361 if (create) {
1362 // Kill incremental widget
1363 if (m_incUi != nullptr) {
1364 // Backup current settings
1365 const bool OF_INCREMENTAL = false;
1366 backupConfig(OF_INCREMENTAL);
1367
1368 // Kill widget
1369 delete m_incUi;
1370 m_incUi = nullptr;
1371 m_layout->removeWidget(m_widget);
1372 m_widget->deleteLater(); // I didn't get a crash here but for symmetrie to the other mutate slot^
1373 }
1374
1375 // Add power widget
1376 m_widget = new QWidget(this);
1377 m_powerUi = new Ui::PowerSearchBar;
1378 m_powerUi->setupUi(m_widget);
1379 m_layout->addWidget(m_widget);
1380
1381 // Bind to shared history models
1382 m_powerUi->pattern->setDuplicatesEnabled(false);
1383 m_powerUi->pattern->setInsertPolicy(QComboBox::InsertAtTop);
1384 m_powerUi->pattern->setMaxCount(m_config->maxHistorySize());
1385 m_powerUi->pattern->setModel(KTextEditor::EditorPrivate::self()->searchHistoryModel());
1386 m_powerUi->pattern->lineEdit()->setClearButtonEnabled(true);
1387 m_powerUi->pattern->setCompleter(nullptr);
1388 m_powerUi->replacement->setDuplicatesEnabled(false);
1389 m_powerUi->replacement->setInsertPolicy(QComboBox::InsertAtTop);
1390 m_powerUi->replacement->setMaxCount(m_config->maxHistorySize());
1391 m_powerUi->replacement->setModel(KTextEditor::EditorPrivate::self()->replaceHistoryModel());
1392 m_powerUi->replacement->lineEdit()->setClearButtonEnabled(true);
1393 m_powerUi->replacement->setCompleter(nullptr);
1394
1395 // Filter Up/Down arrow key inputs to save unfinished search/replace text
1396 m_powerUi->pattern->installEventFilter(this);
1397 m_powerUi->replacement->installEventFilter(this);
1398
1399 // Icons
1400 // Gnome does not seem to have all icons we want, so we use fall-back icons for those that are missing.
1401 QIcon mutateIcon = QIcon::fromTheme(QStringLiteral("games-config-options"), QIcon::fromTheme(QStringLiteral("preferences-system")));
1402 QIcon matchCaseIcon = QIcon::fromTheme(QStringLiteral("format-text-superscript"), QIcon::fromTheme(QStringLiteral("format-text-bold")));
1403 m_powerUi->mutate->setIcon(mutateIcon);
1404 m_powerUi->mutate->setChecked(true);
1405 m_powerUi->findNext->setIcon(QIcon::fromTheme(QStringLiteral("go-down-search")));
1406 m_powerUi->findPrev->setIcon(QIcon::fromTheme(QStringLiteral("go-up-search")));
1407 m_powerUi->findAll->setIcon(QIcon::fromTheme(QStringLiteral("edit-find")));
1408 m_powerUi->matchCase->setIcon(matchCaseIcon);
1409 m_powerUi->selectionOnly->setIcon(QIcon::fromTheme(QStringLiteral("edit-select-all")));
1410
1411 // Focus proxy
1412 centralWidget()->setFocusProxy(m_powerUi->pattern);
1413 }
1414
1415 m_powerUi->selectionOnly->setChecked(selectionOnly);
1416
1417 // Restore previous settings
1418 if (create) {
1419 m_powerUi->matchCase->setChecked(m_powerMatchCase);
1420 m_powerUi->searchMode->setCurrentIndex(m_powerMode);
1421 }
1422
1423 // force current index of -1 --> <cursor down> shows 1st completion entry instead of 2nd
1424 m_powerUi->pattern->setCurrentIndex(-1);
1425 m_powerUi->replacement->setCurrentIndex(-1);
1426
1427 // Set initial search pattern
1428 QLineEdit *const patternLineEdit = m_powerUi->pattern->lineEdit();
1429 Q_ASSERT(patternLineEdit != nullptr);
1430 patternLineEdit->setText(initialPattern);
1431 patternLineEdit->selectAll();
1432
1433 // Set initial replacement text
1434 QLineEdit *const replacementLineEdit = m_powerUi->replacement->lineEdit();
1435 Q_ASSERT(replacementLineEdit != nullptr);
1436 replacementLineEdit->setText(m_replacement);
1437
1438 // Propagate settings (slots are still inactive on purpose)
1439 onPowerPatternChanged(initialPattern);
1440 givePatternFeedback();
1441
1442 if (create) {
1443 // Slots
1444 connect(m_powerUi->mutate, &QToolButton::clicked, this, &KateSearchBar::enterIncrementalMode);
1445 connect(patternLineEdit, &QLineEdit::textChanged, this, &KateSearchBar::onPowerPatternChanged);
1446 connect(m_powerUi->findNext, &QToolButton::clicked, this, &KateSearchBar::findNext);
1447 connect(m_powerUi->findPrev, &QToolButton::clicked, this, &KateSearchBar::findPrevious);
1448 connect(m_powerUi->replaceNext, &QPushButton::clicked, this, &KateSearchBar::replaceNext);
1449 connect(m_powerUi->replaceAll, &QPushButton::clicked, this, &KateSearchBar::replaceAll);
1450 connect(m_powerUi->searchMode, &QComboBox::currentIndexChanged, this, &KateSearchBar::onPowerModeChanged);
1451 connect(m_powerUi->matchCase, &QToolButton::toggled, this, &KateSearchBar::onMatchCaseToggled);
1452 connect(m_powerUi->findAll, &QPushButton::clicked, this, &KateSearchBar::findAll);
1453 connect(m_powerUi->cancel, &QPushButton::clicked, this, &KateSearchBar::onPowerCancelFindOrReplace);
1454
1455 // Make [return] in pattern line edit trigger <find next> action
1456 connect(patternLineEdit, &QLineEdit::returnPressed, this, &KateSearchBar::onReturnPressed);
1457 connect(replacementLineEdit, &QLineEdit::returnPressed, this, &KateSearchBar::replaceNext);
1458
1459 // Hook into line edit context menus
1460 m_powerUi->pattern->setContextMenuPolicy(Qt::CustomContextMenu);
1461
1462 connect(m_powerUi->pattern, &QComboBox::customContextMenuRequested, this, qOverload<const QPoint &>(&KateSearchBar::onPowerPatternContextMenuRequest));
1463 m_powerUi->replacement->setContextMenuPolicy(Qt::CustomContextMenu);
1464 connect(m_powerUi->replacement,
1466 this,
1467 qOverload<const QPoint &>(&KateSearchBar::onPowerReplacmentContextMenuRequest));
1468 }
1469
1470 // Focus
1471 if (m_widget->isVisible()) {
1472 m_powerUi->pattern->setFocus(Qt::MouseFocusReason);
1473 }
1474
1475 // move close button to right layout, ensures properly at top for both incremental + advanced mode
1476 m_powerUi->gridLayout->addWidget(closeButton(), 0, 2, 1, 1);
1477}
1478
1479void KateSearchBar::enterIncrementalMode()
1480{
1481 QString initialPattern;
1482
1483 // Guess settings from context: init pattern with current selection
1484 const bool selected = m_view->selection();
1485 if (selected) {
1486 const Range &selection = m_view->selectionRange();
1487 if (selection.onSingleLine()) {
1488 // ... with current selection
1489 initialPattern = m_view->selectionText();
1490 }
1491 }
1492
1493 // If there's no new selection, we'll use the existing pattern
1494 if (initialPattern.isNull()) {
1495 // Coming from incremental search?
1496 const bool fromIncremental = (m_incUi != nullptr) && (m_widget->isVisible());
1497 if (fromIncremental) {
1498 m_incUi->pattern->lineEdit()->selectAll();
1499 m_incUi->pattern->setFocus(Qt::MouseFocusReason);
1500 return;
1501 }
1502
1503 // Coming from power search?
1504 const bool fromReplace = (m_powerUi != nullptr) && (m_widget->isVisible());
1505 if (fromReplace) {
1506 initialPattern = m_powerUi->pattern->currentText();
1507 // current text will be used as initial replacement text later
1508 m_replacement = m_powerUi->replacement->currentText();
1509 }
1510 }
1511
1512 // Still no search pattern? Use the word under the cursor
1513 if (initialPattern.isNull()) {
1514 const KTextEditor::Cursor cursorPosition = m_view->cursorPosition();
1515 initialPattern = m_view->doc()->wordAt(cursorPosition);
1516 }
1517
1518 // Create dialog
1519 const bool create = (m_incUi == nullptr);
1520 if (create) {
1521 // Kill power widget
1522 if (m_powerUi != nullptr) {
1523 // Backup current settings
1524 const bool OF_POWER = true;
1525 backupConfig(OF_POWER);
1526
1527 // Kill widget
1528 delete m_powerUi;
1529 m_powerUi = nullptr;
1530 m_layout->removeWidget(m_widget);
1531 m_widget->deleteLater(); // deleteLater, because it's not a good idea too delete the widget and there for the button triggering this slot
1532 }
1533
1534 // Add incremental widget
1535 m_widget = new QWidget(this);
1536 m_incUi = new Ui::IncrementalSearchBar;
1537 m_incUi->setupUi(m_widget);
1538 m_layout->addWidget(m_widget);
1539
1540 // Filter Up/Down arrow key inputs to save unfinished search text
1541 m_incUi->pattern->installEventFilter(this);
1542
1543 // new QShortcut(KStandardShortcut::paste().primary(), m_incUi->pattern, SLOT(paste()), 0, Qt::WidgetWithChildrenShortcut);
1544 // if (!KStandardShortcut::paste().alternate().isEmpty())
1545 // new QShortcut(KStandardShortcut::paste().alternate(), m_incUi->pattern, SLOT(paste()), 0, Qt::WidgetWithChildrenShortcut);
1546
1547 // Icons
1548 // Gnome does not seem to have all icons we want, so we use fall-back icons for those that are missing.
1549 QIcon mutateIcon = QIcon::fromTheme(QStringLiteral("games-config-options"), QIcon::fromTheme(QStringLiteral("preferences-system")));
1550 QIcon matchCaseIcon = QIcon::fromTheme(QStringLiteral("format-text-superscript"), QIcon::fromTheme(QStringLiteral("format-text-bold")));
1551 m_incUi->mutate->setIcon(mutateIcon);
1552 m_incUi->next->setIcon(QIcon::fromTheme(QStringLiteral("go-down-search")));
1553 m_incUi->prev->setIcon(QIcon::fromTheme(QStringLiteral("go-up-search")));
1554 m_incUi->matchCase->setIcon(matchCaseIcon);
1555
1556 // Ensure minimum size
1557 m_incUi->pattern->setMinimumWidth(12 * m_incUi->pattern->fontMetrics().height());
1558
1559 // Customize status area
1560 m_incUi->status->setTextElideMode(Qt::ElideLeft);
1561
1562 // Focus proxy
1563 centralWidget()->setFocusProxy(m_incUi->pattern);
1564
1565 m_incUi->pattern->setDuplicatesEnabled(false);
1566 m_incUi->pattern->setInsertPolicy(QComboBox::InsertAtTop);
1567 m_incUi->pattern->setMaxCount(m_config->maxHistorySize());
1568 m_incUi->pattern->setModel(KTextEditor::EditorPrivate::self()->searchHistoryModel());
1569 m_incUi->pattern->lineEdit()->setClearButtonEnabled(true);
1570 m_incUi->pattern->setCompleter(nullptr);
1571 }
1572
1573 // Restore previous settings
1574 if (create) {
1575 m_incUi->matchCase->setChecked(m_incMatchCase);
1576 }
1577
1578 // force current index of -1 --> <cursor down> shows 1st completion entry instead of 2nd
1579 m_incUi->pattern->setCurrentIndex(-1);
1580
1581 // Set initial search pattern
1582 if (!create) {
1583 disconnect(m_incUi->pattern, &QComboBox::editTextChanged, this, &KateSearchBar::onIncPatternChanged);
1584 }
1585 m_incUi->pattern->setEditText(initialPattern);
1586 connect(m_incUi->pattern, &QComboBox::editTextChanged, this, &KateSearchBar::onIncPatternChanged);
1587 m_incUi->pattern->lineEdit()->selectAll();
1588
1589 // Propagate settings (slots are still inactive on purpose)
1590 if (initialPattern.isEmpty()) {
1591 // Reset edit color
1592 indicateMatch(MatchNothing);
1593 }
1594
1595 // Enable/disable next/prev
1596 m_incUi->next->setDisabled(initialPattern.isEmpty());
1597 m_incUi->prev->setDisabled(initialPattern.isEmpty());
1598
1599 if (create) {
1600 // Slots
1601 connect(m_incUi->mutate, &QToolButton::clicked, this, &KateSearchBar::enterPowerMode);
1602 connect(m_incUi->pattern->lineEdit(), &QLineEdit::returnPressed, this, &KateSearchBar::onReturnPressed);
1603 connect(m_incUi->next, &QToolButton::clicked, this, &KateSearchBar::findNext);
1604 connect(m_incUi->prev, &QToolButton::clicked, this, &KateSearchBar::findPrevious);
1605 connect(m_incUi->matchCase, &QToolButton::toggled, this, &KateSearchBar::onMatchCaseToggled);
1606 }
1607
1608 // Focus
1609 if (m_widget->isVisible()) {
1610 m_incUi->pattern->setFocus(Qt::MouseFocusReason);
1611 }
1612
1613 // move close button to right layout, ensures properly at top for both incremental + advanced mode
1614 m_incUi->hboxLayout->addWidget(closeButton());
1615}
1616
1617bool KateSearchBar::clearHighlights()
1618{
1619 // Remove ScrollBarMarks
1620 const QHash<int, KTextEditor::Mark *> &marks = m_view->document()->marks();
1622 while (i.hasNext()) {
1623 i.next();
1624 if (i.value()->type & KTextEditor::Document::SearchMatch) {
1625 m_view->document()->removeMark(i.value()->line, KTextEditor::Document::SearchMatch);
1626 }
1627 }
1628
1629 if (m_infoMessage) {
1630 delete m_infoMessage;
1631 }
1632
1633 if (m_hlRanges.isEmpty()) {
1634 return false;
1635 }
1636 qDeleteAll(m_hlRanges);
1637 m_hlRanges.clear();
1638 return true;
1639}
1640
1641void KateSearchBar::updateHighlightColors()
1642{
1643 const QColor foregroundColor = m_view->defaultStyleAttribute(KSyntaxHighlighting::Theme::TextStyle::Normal)->foreground().color();
1644 const QColor &searchColor = m_view->rendererConfig()->searchHighlightColor();
1645 const QColor &replaceColor = m_view->rendererConfig()->replaceHighlightColor();
1646
1647 // init match attribute
1648 highlightMatchAttribute->setForeground(foregroundColor);
1649 highlightMatchAttribute->setBackground(searchColor);
1650 highlightMatchAttribute->dynamicAttribute(Attribute::ActivateMouseIn)->setBackground(searchColor);
1651 highlightMatchAttribute->dynamicAttribute(Attribute::ActivateMouseIn)->setForeground(foregroundColor);
1652 highlightMatchAttribute->dynamicAttribute(Attribute::ActivateCaretIn)->setBackground(searchColor);
1653 highlightMatchAttribute->dynamicAttribute(Attribute::ActivateCaretIn)->setForeground(foregroundColor);
1654
1655 // init replacement attribute
1656 highlightReplacementAttribute->setBackground(replaceColor);
1657 highlightReplacementAttribute->setForeground(foregroundColor);
1658}
1659
1660void KateSearchBar::showEvent(QShowEvent *event)
1661{
1662 // Update init cursor
1663 if (m_incUi != nullptr) {
1664 m_incInitCursor = m_view->cursorPosition();
1665 }
1666
1667 // We don't want to update if a "findOrReplaceAll" is running
1668 // other we end up deleting our working range and crash
1669 if (m_cancelFindOrReplace) {
1670 updateSelectionOnly();
1671 }
1672
1674}
1675
1676bool KateSearchBar::eventFilter(QObject *obj, QEvent *event)
1677{
1679 if (combo && event->type() == QEvent::KeyPress) {
1680 const int key = static_cast<QKeyEvent *>(event)->key();
1681 const int currentIndex = combo->currentIndex();
1682 const QString currentText = combo->currentText();
1683 QString &unfinishedText = (m_powerUi && combo == m_powerUi->replacement) ? m_replacement : m_unfinishedSearchText;
1684 if (key == Qt::Key_Up && currentIndex <= 0 && unfinishedText != currentText) {
1685 // Only restore unfinished text if we are already in the latest entry
1686 combo->setCurrentIndex(-1);
1687 combo->setCurrentText(unfinishedText);
1688 } else if (key == Qt::Key_Down || key == Qt::Key_Up) {
1689 // Only save unfinished text if it is not empty and it is modified
1690 const bool isUnfinishedSearch = (!currentText.trimmed().isEmpty() && (currentIndex == -1 || combo->itemText(currentIndex) != currentText));
1691 if (isUnfinishedSearch && unfinishedText != currentText) {
1692 unfinishedText = currentText;
1693 }
1694 }
1695 }
1696
1697 return QWidget::eventFilter(obj, event);
1698}
1699
1700void KateSearchBar::updateSelectionOnly()
1701{
1702 // Make sure the previous selection-only search range is not used anymore
1703 if (m_workingRange && !m_selectionChangedByUndoRedo) {
1704 delete m_workingRange;
1705 m_workingRange = nullptr;
1706 }
1707
1708 if (m_powerUi == nullptr) {
1709 return;
1710 }
1711
1712 // Re-init "Selection only" checkbox if power search bar open
1713 const bool selected = m_view->selection();
1714 bool selectionOnly = selected;
1715 if (selected) {
1716 Range const &selection = m_view->selectionRange();
1717 selectionOnly = !selection.onSingleLine();
1718 }
1719 m_powerUi->selectionOnly->setChecked(selectionOnly);
1720}
1721
1722void KateSearchBar::updateIncInitCursor()
1723{
1724 if (m_incUi == nullptr) {
1725 return;
1726 }
1727
1728 // Update init cursor
1729 m_incInitCursor = m_view->cursorPosition();
1730}
1731
1732void KateSearchBar::onPowerPatternContextMenuRequest(const QPoint &pos)
1733{
1734 const bool FOR_PATTERN = true;
1735 showExtendedContextMenu(FOR_PATTERN, pos);
1736}
1737
1738void KateSearchBar::onPowerPatternContextMenuRequest()
1739{
1740 onPowerPatternContextMenuRequest(m_powerUi->pattern->mapFromGlobal(QCursor::pos()));
1741}
1742
1743void KateSearchBar::onPowerReplacmentContextMenuRequest(const QPoint &pos)
1744{
1745 const bool FOR_REPLACEMENT = false;
1746 showExtendedContextMenu(FOR_REPLACEMENT, pos);
1747}
1748
1749void KateSearchBar::onPowerReplacmentContextMenuRequest()
1750{
1751 onPowerReplacmentContextMenuRequest(m_powerUi->replacement->mapFromGlobal(QCursor::pos()));
1752}
1753
1754void KateSearchBar::onPowerCancelFindOrReplace()
1755{
1756 m_cancelFindOrReplace = true;
1757}
1758
1759bool KateSearchBar::isPower() const
1760{
1761 return m_powerUi != nullptr;
1762}
1763
1764void KateSearchBar::slotReadWriteChanged()
1765{
1766 if (!KateSearchBar::isPower()) {
1767 return;
1768 }
1769
1770 // perhaps disable/enable
1771 m_powerUi->replaceNext->setEnabled(m_view->doc()->isReadWrite() && isPatternValid());
1772 m_powerUi->replaceAll->setEnabled(m_view->doc()->isReadWrite() && isPatternValid());
1773}
1774
1775#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())
bool isReadWrite() const
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.
Backend of KTextEditor::Document related public KTextEditor interfaces.
KTextEditor::Cursor documentEnd() const override
End position of the document.
bool postMessage(KTextEditor::Message *message) override
Post message to the Document and its Views.
KTextEditor::MovingRange * newMovingRange(KTextEditor::Range range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors=KTextEditor::MovingRange::DoNotExpand, KTextEditor::MovingRange::EmptyBehavior emptyBehavior=KTextEditor::MovingRange::AllowEmpty) override
Create a new moving range for this document.
QString text(KTextEditor::Range range, bool blockwise=false) const override
Get the document content within the given range.
QString wordAt(KTextEditor::Cursor cursor) const override
Get the word at the text position cursor.
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.
KateUndoManager implements a document's history.
KTEXTEDITOR_EXPORT void undoSafePoint()
Prevent latest KateUndoGroup from being merged with the next one.
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)
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
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()
The KTextEditor namespace contains all the public API that is required to use the KTextEditor compone...
@ Default
Default settings.
Definition document.h:48
@ CaseInsensitive
Ignores cases, e.g. "a" matches "A".
Definition document.h:54
@ Regex
Treats the pattern as a regular expression.
Definition document.h:51
@ Backwards
Searches in backward direction.
Definition document.h:55
@ EscapeSequences
Plaintext mode: Processes escape sequences.
Definition document.h:58
@ WholeWords
Plaintext mode: Whole words only, e.g. not "amp" in "example".
Definition document.h:59
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()
bool testFlag(Enum flag) const const
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)
T qobject_cast(QObject *object)
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
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 Fri Jul 26 2024 11:56:21 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.