diff --git a/src/buffer/katetextblock.cpp b/src/buffer/katetextblock.cpp --- a/src/buffer/katetextblock.cpp +++ b/src/buffer/katetextblock.cpp @@ -418,13 +418,13 @@ cursor->m_column = textOfLine.size(); } - // remember range, if any - if (cursor->kateRange()) { + // we only need to trigger checkValidity later if the range has feedback or might be invalidated + if (cursor->kateRange() && (cursor->kateRange()->feedback() || cursor->kateRange()->start().line() == cursor->kateRange()->end().line())) { changedRanges.insert(cursor->kateRange()); } } - // check validity of all ranges, might invalidate them... + // we might need to invalidate ranges or notify about their changes foreach (TextRange *range, changedRanges) { range->checkValidity(); } @@ -487,13 +487,13 @@ cursor->m_column -= (range.end().column() - range.start().column()); } - // remember range, if any - if (cursor->kateRange()) { + // we only need to trigger checkValidity later if the range has feedback or might be invalidated + if (cursor->kateRange() && (cursor->kateRange()->feedback() || cursor->kateRange()->start().line() == cursor->kateRange()->end().line())) { changedRanges.insert(cursor->kateRange()); } } - // check validity of all ranges, might invalidate them... + // we might need to invalidate ranges or notify about their changes foreach (TextRange *range, changedRanges) { range->checkValidity(); } diff --git a/src/document/katedocument.cpp b/src/document/katedocument.cpp --- a/src/document/katedocument.cpp +++ b/src/document/katedocument.cpp @@ -352,16 +352,36 @@ if (m_editingStackPosition != m_editingStack.size() - 1) { m_editingStack.resize(m_editingStackPosition); } - KTextEditor::MovingInterface *moving = qobject_cast(this); + const auto c = range.start(); - QSharedPointer mc (moving->newMovingCursor(c)); + + // try to be clever: reuse existing cursors if possible + QSharedPointer mc; + + // we might pop last one: reuse that if (!m_editingStack.isEmpty() && c.line() == m_editingStack.top()->line()) { - m_editingStack.pop(); + mc = m_editingStack.pop(); } - m_editingStack.push(mc); - if (m_editingStack.size() > s_editingStackSizeLimit) { - m_editingStack.removeFirst(); + + // we might expire oldest one, reuse that one, if not already one there + // we prefer the other one for reuse, as already on the right line aka in the right block! + if (m_editingStack.size() >= s_editingStackSizeLimit) { + if (mc) { + m_editingStack.removeFirst(); + } else { + mc = m_editingStack.takeFirst(); + } } + + // new cursor needed? or adjust existing one? + if (mc) { + mc->setPosition(c); + } else { + mc = QSharedPointer (newMovingCursor(c)); + } + + // add new one as top of stack + m_editingStack.push(mc); m_editingStackPosition = m_editingStack.size() - 1; } @@ -728,22 +748,20 @@ } } + // compute expanded column for block mode + int positionColumnExpanded = insertColumn; const int tabWidth = config()->tabWidth(); - - static const QChar newLineChar(QLatin1Char('\n')); - - int insertColumnExpanded = insertColumn; - Kate::TextLine l = plainKateTextLine(currentLine); - if (l) { - insertColumnExpanded = l->toVirtualColumn(insertColumn, tabWidth); + if (block) { + if (auto l = plainKateTextLine(currentLine)) { + positionColumnExpanded = l->toVirtualColumn(insertColumn, tabWidth); + } } - int positionColumnExpanded = insertColumnExpanded; int pos = 0; for (; pos < totalLength; pos++) { const QChar &ch = text.at(pos); - if (ch == newLineChar) { + if (ch == QLatin1Char('\n')) { // Only perform the text insert if there is text to insert if (currentLineStart < pos) { editInsertText(currentLine, insertColumn, text.mid(currentLineStart, pos - currentLineStart)); @@ -755,9 +773,9 @@ } currentLine++; - l = plainKateTextLine(currentLine); if (block) { + auto l = plainKateTextLine(currentLine); if (currentLine == lastLine() + 1) { editInsertLine(currentLine, QString()); } @@ -768,9 +786,6 @@ } currentLineStart = pos + 1; - if (l) { - insertColumnExpanded = l->toVirtualColumn(insertColumn, tabWidth); - } } } diff --git a/src/search/katesearchbar.cpp b/src/search/katesearchbar.cpp --- a/src/search/katesearchbar.cpp +++ b/src/search/katesearchbar.cpp @@ -49,6 +49,9 @@ #include #include +#include +#include + // Turn debug messages on/off here // #define FAST_DEBUG_ENABLE @@ -803,15 +806,19 @@ const bool regexMode = enabledOptions.testFlag(Regex); const bool multiLinePattern = regexMode ? KateRegExp(searchPattern()).isMultiLine() : false; - KTextEditor::MovingRange *workingRange = m_view->doc()->newMovingRange(inputRange); - QList highlightRanges; + std::unique_ptr workingRange(m_view->doc()->newMovingRange(inputRange)); int matchCounter = 0; + // we highlight all ranges of a replace, up to some hard limit + // e.g. if you replace 100000 things, rendering will break down otherwise ;=) + const int maxHighlightings = 65536; + std::vector highlightRanges; + bool block = m_view->selection() && m_view->blockSelection(); int line = inputRange.start().line(); do { if (block) { - workingRange = m_view->doc()->newMovingRange(m_view->doc()->rangeOnLine(inputRange, line)); + workingRange.reset(m_view->doc()->newMovingRange(m_view->doc()->rangeOnLine(inputRange, line))); } for (;;) { @@ -823,29 +830,32 @@ bool const originalMatchEmpty = match.isEmpty(); // Work with the match + Range lastRange; if (replacement != nullptr) { if (matchCounter == 0) { static_cast(m_view->document())->startEditing(); } // Replace - const Range afterReplace = match.replace(*replacement, false, ++matchCounter); + lastRange = match.replace(*replacement, false, ++matchCounter); + } else { + lastRange = match.range(); + ++matchCounter; + } - // Highlight and continue after adjusted match - //highlightReplacement(*afterReplace); - highlightRanges << afterReplace; + // remember ranges if limit not reached + if (matchCounter < maxHighlightings) { + highlightRanges.push_back(lastRange); } else { - // Highlight and continue after original match - //highlightMatch(match); - highlightRanges << match.range(); - matchCounter++; + highlightRanges.clear(); } // Continue after match - if (highlightRanges.last().end() >= workingRange->end()) { + if (lastRange.end() >= workingRange->end()) { break; } - KTextEditor::DocumentCursor workingStart(m_view->doc(), highlightRanges.last().end()); + + KTextEditor::DocumentCursor workingStart(m_view->doc(), lastRange.end()); if (originalMatchEmpty) { // Can happen for regex patterns like "^". // If we don't advance here we will loop forever... @@ -873,26 +883,27 @@ } // Add ScrollBarMarks - KTextEditor::MarkInterface* iface = qobject_cast(m_view->document()); - if (iface) { - iface->setMarkDescription(KTextEditor::MarkInterface::SearchMatch, i18n("SearchHighLight")); - iface->setMarkPixmap(KTextEditor::MarkInterface::SearchMatch, QIcon().pixmap(0,0)); - foreach (Range r, highlightRanges) { - iface->addMark(r.start().line(), KTextEditor::MarkInterface::SearchMatch); + if (!highlightRanges.empty()) { + KTextEditor::MarkInterface* iface = qobject_cast(m_view->document()); + if (iface) { + iface->setMarkDescription(KTextEditor::MarkInterface::SearchMatch, i18n("SearchHighLight")); + iface->setMarkPixmap(KTextEditor::MarkInterface::SearchMatch, QIcon().pixmap(0,0)); + for (const Range &r : highlightRanges) { + iface->addMark(r.start().line(), KTextEditor::MarkInterface::SearchMatch); + } } } // Add highlights - if (replacement == nullptr) - foreach (Range r, highlightRanges) { + if (replacement == nullptr) { + for (const Range &r : highlightRanges) { highlightMatch(r); } - else - foreach (Range r, highlightRanges) { + } else { + for (const Range &r : highlightRanges) { highlightReplacement(r); } - - delete workingRange; + } // restore connection connect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(updateSelectionOnly())); diff --git a/src/vimode/marks.h b/src/vimode/marks.h --- a/src/vimode/marks.h +++ b/src/vimode/marks.h @@ -69,7 +69,7 @@ void syncViMarksAndBookmarks(); bool isShowable(const QChar &mark); - void setMark(const QChar &mark, const KTextEditor::Cursor &pos, const bool moveoninsert = true); + void setMark(const QChar &mark, const KTextEditor::Cursor &pos); private Q_SLOTS: void markChanged(KTextEditor::Document *doc, diff --git a/src/vimode/marks.cpp b/src/vimode/marks.cpp --- a/src/vimode/marks.cpp +++ b/src/vimode/marks.cpp @@ -79,44 +79,50 @@ config.writeEntry("ViMarks", l); } -void Marks::setMark(const QChar &_mark, const KTextEditor::Cursor &pos, const bool moveoninsert) +void Marks::setMark(const QChar &_mark, const KTextEditor::Cursor &pos) { + // move on insert is type based, this allows to reuse cursors! + // reuse is important for editing intensive things like replace-all + const bool moveoninsert = _mark != BeginEditYanked; + m_settingMark = true; - uint marktype = m_doc->mark(pos.line()); // ` and ' is the same register (position before jump) const QChar mark = (_mark == BeforeJumpAlter) ? BeforeJump : _mark; - // delete old cursor if any + // if we have already a cursor for this type: adjust it + bool needToAdjustVisibleMark = true; if (KTextEditor::MovingCursor *oldCursor = m_marks.value(mark)) { - - int number_of_marks = 0; - - foreach (QChar c, m_marks.keys()) { - if (m_marks.value(c)->line() == oldCursor->line()) { - number_of_marks++; + // cleanup mark display only if line changes + needToAdjustVisibleMark = oldCursor->line() != pos.line(); + if (needToAdjustVisibleMark) { + int number_of_marks = 0; + foreach (QChar c, m_marks.keys()) { + if (m_marks.value(c)->line() == oldCursor->line()) { + number_of_marks++; + } + } + if (number_of_marks == 1) { + m_doc->removeMark(oldCursor->line(), KTextEditor::MarkInterface::markType01); } } - if (number_of_marks == 1 && pos.line() != oldCursor->line()) { - m_doc->removeMark(oldCursor->line(), KTextEditor::MarkInterface::markType01); - } - - delete oldCursor; + // adjust position + oldCursor->setPosition(pos); + } else { + // if no old mark of that type, create new one + const KTextEditor::MovingCursor::InsertBehavior behavior = moveoninsert ? KTextEditor::MovingCursor::MoveOnInsert : KTextEditor::MovingCursor::StayOnInsert; + m_marks.insert(mark, m_doc->newMovingCursor(pos, behavior)); } - KTextEditor::MovingCursor::InsertBehavior behavior = moveoninsert ? KTextEditor::MovingCursor::MoveOnInsert : KTextEditor::MovingCursor::StayOnInsert; - // create and remember new one - m_marks.insert(mark, m_doc->newMovingCursor(pos, behavior)); - - // Showing what mark we set: + // Showing what mark we set, can be skipped if we did not change the line if (isShowable(mark)) { - if (!(marktype & KTextEditor::MarkInterface::markType01)) { + if (needToAdjustVisibleMark && !(m_doc->mark(pos.line()) & KTextEditor::MarkInterface::markType01)) { m_doc->addMark(pos.line(), KTextEditor::MarkInterface::markType01); } // only show message for active view - if ( m_inputModeManager->view()->viewInputMode() == KTextEditor::View::ViInputMode ) { + if (m_inputModeManager->view()->viewInputMode() == KTextEditor::View::ViInputMode) { if (m_doc->activeView() == m_inputModeManager->view()) { m_inputModeManager->getViNormalMode()->message(i18n("Mark set: %1", mark)); } @@ -244,7 +250,7 @@ void Marks::setStartEditYanked(const KTextEditor::Cursor &pos) { - setMark(BeginEditYanked, pos, false); + setMark(BeginEditYanked, pos); } void Marks::setFinishEditYanked(const KTextEditor::Cursor &pos)