File indexing completed on 2024-05-12 07:53:41

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2002 John Firebaugh <jfirebaugh@kde.org>
0004     SPDX-FileCopyrightText: 2002 Joseph Wenninger <jowenn@kde.org>
0005     SPDX-FileCopyrightText: 2002, 2003 Christoph Cullmann <cullmann@kde.org>
0006     SPDX-FileCopyrightText: 2002-2007 Hamish Rodda <rodda@kde.org>
0007     SPDX-FileCopyrightText: 2003 Anakim Border <aborder@sources.sourceforge.net>
0008     SPDX-FileCopyrightText: 2007 Mirko Stocker <me@misto.ch>
0009     SPDX-FileCopyrightText: 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
0010     SPDX-FileCopyrightText: 2008 Erlend Hamberg <ehamberg@gmail.com>
0011 
0012     Based on KWriteView:
0013     SPDX-FileCopyrightText: 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de>
0014 
0015     SPDX-License-Identifier: LGPL-2.0-only
0016 */
0017 #include "kateviewinternal.h"
0018 
0019 #include "kateabstractinputmode.h"
0020 #include "kateabstractinputmodefactory.h"
0021 #include "katebuffer.h"
0022 #include "katecompletionwidget.h"
0023 #include "kateconfig.h"
0024 #include "kateglobal.h"
0025 #include "katehighlight.h"
0026 #include "katelayoutcache.h"
0027 #include "katemessagewidget.h"
0028 #include "katepartdebug.h"
0029 #include "katerenderer.h"
0030 #include "katetextanimation.h"
0031 #include "katetextpreview.h"
0032 #include "kateview.h"
0033 #include "kateviewaccessible.h"
0034 #include "kateviewhelpers.h"
0035 #include "spellcheck/spellingmenu.h"
0036 
0037 #include <KCursor>
0038 #include <ktexteditor/documentcursor.h>
0039 #include <ktexteditor/inlinenoteprovider.h>
0040 #include <ktexteditor/movingrange.h>
0041 #include <ktexteditor/texthintinterface.h>
0042 
0043 #include <QAccessible>
0044 #include <QApplication>
0045 #include <QClipboard>
0046 #include <QKeyEvent>
0047 #include <QLayout>
0048 #include <QMenu>
0049 #include <QMimeData>
0050 #include <QPainter>
0051 #include <QPixmap>
0052 #include <QScopeGuard>
0053 #include <QScroller>
0054 #include <QStyle>
0055 #include <QToolTip>
0056 
0057 static const bool debugPainting = false;
0058 
0059 class ZoomEventFilter
0060 {
0061 public:
0062     ZoomEventFilter() = default;
0063 
0064     bool detectZoomingEvent(QWheelEvent *e, Qt::KeyboardModifiers modifier = Qt::ControlModifier)
0065     {
0066         Qt::KeyboardModifiers modState = e->modifiers();
0067         if (modState == modifier) {
0068             if (m_lastWheelEvent.isValid()) {
0069                 const qint64 deltaT = m_lastWheelEvent.elapsed();
0070                 // Pressing the specified modifier key within 200ms of the previous "unmodified"
0071                 // wheelevent is not allowed to toggle on text zooming
0072                 if (m_lastWheelEventUnmodified && deltaT < 200) {
0073                     m_ignoreZoom = true;
0074                 } else if (deltaT > 1000) {
0075                     // the protection is kept active for 1s after the last wheel event
0076                     // TODO: this value should be tuned, preferably by someone using
0077                     // Ctrl+Wheel zooming frequently.
0078                     m_ignoreZoom = false;
0079                 }
0080             } else {
0081                 // we can't say anything and have to assume there's nothing
0082                 // accidental to the modifier being pressed.
0083                 m_ignoreZoom = false;
0084             }
0085             m_lastWheelEventUnmodified = false;
0086             if (m_ignoreZoom) {
0087                 // unset the modifier so the view scrollbars can handle the scroll
0088                 // event and produce normal, not accelerated scrolling
0089                 modState &= ~modifier;
0090                 e->setModifiers(modState);
0091             }
0092         } else {
0093             // state is reset after any wheel event without the zoom modifier
0094             m_lastWheelEventUnmodified = true;
0095             m_ignoreZoom = false;
0096         }
0097         m_lastWheelEvent.start();
0098 
0099         // inform the caller whether this event is allowed to trigger text zooming.
0100         return !m_ignoreZoom && modState == modifier;
0101     }
0102 
0103 protected:
0104     QElapsedTimer m_lastWheelEvent;
0105     bool m_ignoreZoom = false;
0106     bool m_lastWheelEventUnmodified = false;
0107 };
0108 
0109 KateViewInternal::KateViewInternal(KTextEditor::ViewPrivate *view)
0110     : QWidget(view)
0111     , editSessionNumber(0)
0112     , editIsRunning(false)
0113     , m_view(view)
0114     , m_cursor(doc()->buffer(), KTextEditor::Cursor(0, 0), Kate::TextCursor::MoveOnInsert)
0115     , m_mouse()
0116     , m_possibleTripleClick(false)
0117     , m_bm(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand))
0118     , m_bmStart(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand))
0119     , m_bmEnd(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand))
0120     , m_bmLastFlashPos(doc()->newMovingCursor(KTextEditor::Cursor::invalid()))
0121 
0122     // folding marker
0123     , m_fmStart(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand))
0124     , m_fmEnd(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand))
0125 
0126     , m_dummy(nullptr)
0127 
0128     // stay on cursor will avoid that the view scroll around on press return at beginning
0129     , m_startPos(doc()->buffer(), KTextEditor::Cursor(0, 0), Kate::TextCursor::StayOnInsert)
0130 
0131     , m_visibleLineCount(0)
0132     , m_madeVisible(false)
0133     , m_shiftKeyPressed(false)
0134     , m_autoCenterLines(0)
0135     , m_minLinesVisible(0)
0136     , m_selChangedByUser(false)
0137     , m_selectAnchor(-1, -1)
0138     , m_selectionMode(Default)
0139     , m_layoutCache(new KateLayoutCache(renderer(), this))
0140     , m_preserveX(false)
0141     , m_preservedX(0)
0142     , m_cachedMaxStartPos(-1, -1)
0143     , m_dragScrollTimer(this)
0144     , m_scrollTimer(this)
0145     , m_cursorTimer(this)
0146     , m_textHintTimer(this)
0147     , m_textHintDelay(500)
0148     , m_textHintPos(-1, -1)
0149     , m_imPreeditRange(nullptr)
0150 {
0151     // setup input modes
0152     Q_ASSERT(m_inputModes.size() == KTextEditor::EditorPrivate::self()->inputModeFactories().size());
0153     m_inputModes[KTextEditor::View::NormalInputMode].reset(
0154         KTextEditor::EditorPrivate::self()->inputModeFactories()[KTextEditor::View::NormalInputMode]->createInputMode(this));
0155     m_inputModes[KTextEditor::View::ViInputMode].reset(
0156         KTextEditor::EditorPrivate::self()->inputModeFactories()[KTextEditor::View::ViInputMode]->createInputMode(this));
0157     m_currentInputMode = m_inputModes[KTextEditor::View::NormalInputMode].get();
0158 
0159     setMinimumSize(0, 0);
0160     setAttribute(Qt::WA_OpaquePaintEvent);
0161     setAttribute(Qt::WA_InputMethodEnabled);
0162 
0163     // invalidate m_selectionCached.start(), or keyb selection is screwed initially
0164     m_selectionCached = KTextEditor::Range::invalid();
0165 
0166     // bracket markers are only for this view and should not be printed
0167     m_bm->setView(m_view);
0168     m_bmStart->setView(m_view);
0169     m_bmEnd->setView(m_view);
0170     m_bm->setAttributeOnlyForViews(true);
0171     m_bmStart->setAttributeOnlyForViews(true);
0172     m_bmEnd->setAttributeOnlyForViews(true);
0173 
0174     // use z depth defined in moving ranges interface
0175     m_bm->setZDepth(-1000.0);
0176     m_bmStart->setZDepth(-1000.0);
0177     m_bmEnd->setZDepth(-1000.0);
0178 
0179     // update mark attributes
0180     updateBracketMarkAttributes();
0181 
0182     //
0183     // scrollbar for lines
0184     //
0185     m_lineScroll = new KateScrollBar(Qt::Vertical, this);
0186     m_lineScroll->show();
0187     m_lineScroll->setTracking(true);
0188     m_lineScroll->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
0189 
0190     // Hijack the line scroller's controls, so we can scroll nicely for word-wrap
0191     connect(m_lineScroll, &KateScrollBar::actionTriggered, this, &KateViewInternal::scrollAction);
0192 
0193     auto viewScrollLinesSlot = qOverload<int>(&KateViewInternal::scrollLines);
0194     connect(m_lineScroll, &KateScrollBar::sliderMoved, this, viewScrollLinesSlot);
0195     connect(m_lineScroll, &KateScrollBar::sliderMMBMoved, this, viewScrollLinesSlot);
0196     connect(m_lineScroll, &KateScrollBar::valueChanged, this, viewScrollLinesSlot);
0197 
0198     //
0199     // scrollbar for columns
0200     //
0201     m_columnScroll = new QScrollBar(Qt::Horizontal, m_view);
0202     m_scroller = QScroller::scroller(this);
0203     QScrollerProperties prop;
0204     prop.setScrollMetric(QScrollerProperties::DecelerationFactor, 0.3);
0205     prop.setScrollMetric(QScrollerProperties::MaximumVelocity, 1);
0206     prop.setScrollMetric(QScrollerProperties::AcceleratingFlickMaximumTime,
0207                          0.2); // Workaround for QTBUG-88249 (non-flick gestures recognized as accelerating flick)
0208     prop.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff);
0209     prop.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff);
0210     prop.setScrollMetric(QScrollerProperties::DragStartDistance, 0.0);
0211     m_scroller->setScrollerProperties(prop);
0212 #ifndef Q_OS_MACOS
0213     // On macOS the trackpad also emits touch events which are sometimes picked up by the flick
0214     // gesture recogniser registered by QScroller; this results in some odd scrolling behaviour
0215     // as described in bug #442060.  Therefore it's better to not let the QScroller register
0216     // it on that platform.
0217     m_scroller->grabGesture(this);
0218 #endif
0219 
0220     if (m_view->dynWordWrap()) {
0221         m_columnScroll->hide();
0222     } else {
0223         m_columnScroll->show();
0224     }
0225 
0226     m_columnScroll->setTracking(true);
0227     m_startX = 0;
0228 
0229     connect(m_columnScroll, &QScrollBar::valueChanged, this, &KateViewInternal::scrollColumns);
0230 
0231     // bottom corner box
0232     m_dummy = new QWidget(m_view);
0233     m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height());
0234     m_dummy->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
0235 
0236     if (m_view->dynWordWrap()) {
0237         m_dummy->hide();
0238     } else {
0239         m_dummy->show();
0240     }
0241 
0242     cache()->setWrap(m_view->dynWordWrap());
0243 
0244     //
0245     // iconborder ;)
0246     //
0247     m_leftBorder = new KateIconBorder(this, m_view);
0248     m_leftBorder->show();
0249 
0250     // update view if folding ranges change
0251     connect(&m_view->textFolding(), &Kate::TextFolding::foldingRangesChanged, this, &KateViewInternal::slotRegionVisibilityChanged);
0252 
0253     m_displayCursor.setPosition(0, 0);
0254 
0255     setAcceptDrops(true);
0256 
0257     m_zoomEventFilter.reset(new ZoomEventFilter());
0258     // event filter
0259     installEventFilter(this);
0260 
0261     // set initial cursor
0262     m_mouseCursor = Qt::IBeamCursor;
0263     setCursor(m_mouseCursor);
0264 
0265     // call mouseMoveEvent also if no mouse button is pressed
0266     setMouseTracking(true);
0267 
0268     m_dragInfo.state = diNone;
0269 
0270     // timers
0271     connect(&m_dragScrollTimer, &QTimer::timeout, this, &KateViewInternal::doDragScroll);
0272 
0273     connect(&m_scrollTimer, &QTimer::timeout, this, &KateViewInternal::scrollTimeout);
0274 
0275     connect(&m_cursorTimer, &QTimer::timeout, this, &KateViewInternal::cursorTimeout);
0276 
0277     connect(&m_textHintTimer, &QTimer::timeout, this, &KateViewInternal::textHintTimeout);
0278 
0279     // selection changed to set anchor
0280     connect(m_view, &KTextEditor::ViewPrivate::selectionChanged, this, &KateViewInternal::viewSelectionChanged);
0281 
0282 #ifndef QT_NO_ACCESSIBILITY
0283     QAccessible::installFactory(accessibleInterfaceFactory);
0284 #endif
0285     connect(doc(), &KTextEditor::DocumentPrivate::textInsertedRange, this, &KateViewInternal::documentTextInserted);
0286     connect(doc(), &KTextEditor::DocumentPrivate::textRemoved, this, &KateViewInternal::documentTextRemoved);
0287 
0288     // update is called in KTextEditor::ViewPrivate, after construction and layout is over
0289     // but before any other kateviewinternal call
0290 }
0291 
0292 KateViewInternal::~KateViewInternal()
0293 {
0294     // delete text animation object here, otherwise it updates the view in its destructor
0295     delete m_textAnimation;
0296 
0297 #ifndef QT_NO_ACCESSIBILITY
0298     QAccessible::removeFactory(accessibleInterfaceFactory);
0299 #endif
0300 }
0301 
0302 void KateViewInternal::dynWrapChanged()
0303 {
0304     m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height());
0305     if (view()->dynWordWrap()) {
0306         m_columnScroll->hide();
0307         m_dummy->hide();
0308 
0309     } else {
0310         // column scrollbar + bottom corner box
0311         m_columnScroll->show();
0312         m_dummy->show();
0313     }
0314 
0315     cache()->setWrap(view()->dynWordWrap());
0316     updateView();
0317 
0318     if (view()->dynWordWrap()) {
0319         scrollColumns(0);
0320     }
0321 
0322     update();
0323 }
0324 
0325 KTextEditor::Cursor KateViewInternal::endPos() const
0326 {
0327     // Hrm, no lines laid out at all??
0328     if (!cache()->viewCacheLineCount()) {
0329         return KTextEditor::Cursor();
0330     }
0331 
0332     for (int i = qMin(linesDisplayed() - 1, cache()->viewCacheLineCount() - 1); i >= 0; i--) {
0333         const KateTextLayout &thisLine = cache()->viewLine(i);
0334 
0335         if (thisLine.line() == -1) {
0336             continue;
0337         }
0338 
0339         if (thisLine.virtualLine() >= view()->textFolding().visibleLines()) {
0340             // Cache is too out of date
0341             return KTextEditor::Cursor(view()->textFolding().visibleLines() - 1,
0342                                        doc()->lineLength(view()->textFolding().visibleLineToLine(view()->textFolding().visibleLines() - 1)));
0343         }
0344 
0345         return KTextEditor::Cursor(thisLine.virtualLine(), thisLine.wrap() ? thisLine.endCol() - 1 : thisLine.endCol());
0346     }
0347 
0348     // can happen, if view is still invisible
0349     return KTextEditor::Cursor();
0350 }
0351 
0352 int KateViewInternal::endLine() const
0353 {
0354     return endPos().line();
0355 }
0356 
0357 KateTextLayout KateViewInternal::yToKateTextLayout(int y) const
0358 {
0359     if (y < 0 || y > size().height()) {
0360         return KateTextLayout::invalid();
0361     }
0362 
0363     int range = y / renderer()->lineHeight();
0364 
0365     // lineRanges is always bigger than 0, after the initial updateView call
0366     if (range >= 0 && range < cache()->viewCacheLineCount()) {
0367         return cache()->viewLine(range);
0368     }
0369 
0370     return KateTextLayout::invalid();
0371 }
0372 
0373 int KateViewInternal::lineToY(int viewLine) const
0374 {
0375     return (viewLine - startLine()) * renderer()->lineHeight();
0376 }
0377 
0378 void KateViewInternal::slotIncFontSizes(qreal step)
0379 {
0380     renderer()->increaseFontSizes(step);
0381 }
0382 
0383 void KateViewInternal::slotDecFontSizes(qreal step)
0384 {
0385     renderer()->decreaseFontSizes(step);
0386 }
0387 
0388 void KateViewInternal::slotResetFontSizes()
0389 {
0390     renderer()->resetFontSizes();
0391 }
0392 
0393 /**
0394  * Line is the real line number to scroll to.
0395  */
0396 void KateViewInternal::scrollLines(int line)
0397 {
0398     KTextEditor::Cursor newPos(line, 0);
0399     scrollPos(newPos);
0400 }
0401 
0402 // This can scroll less than one true line
0403 void KateViewInternal::scrollViewLines(int offset)
0404 {
0405     KTextEditor::Cursor c = viewLineOffset(startPos(), offset);
0406     scrollPos(c);
0407 
0408     bool blocked = m_lineScroll->blockSignals(true);
0409     m_lineScroll->setValue(startLine());
0410     m_lineScroll->blockSignals(blocked);
0411 }
0412 
0413 void KateViewInternal::scrollAction(int action)
0414 {
0415     switch (action) {
0416     case QAbstractSlider::SliderSingleStepAdd:
0417         scrollNextLine();
0418         break;
0419 
0420     case QAbstractSlider::SliderSingleStepSub:
0421         scrollPrevLine();
0422         break;
0423 
0424     case QAbstractSlider::SliderPageStepAdd:
0425         scrollNextPage();
0426         break;
0427 
0428     case QAbstractSlider::SliderPageStepSub:
0429         scrollPrevPage();
0430         break;
0431 
0432     case QAbstractSlider::SliderToMinimum:
0433         top_home();
0434         break;
0435 
0436     case QAbstractSlider::SliderToMaximum:
0437         bottom_end();
0438         break;
0439     }
0440 }
0441 
0442 void KateViewInternal::scrollNextPage()
0443 {
0444     scrollViewLines(qMax(linesDisplayed() - 1, 0));
0445 }
0446 
0447 void KateViewInternal::scrollPrevPage()
0448 {
0449     scrollViewLines(-qMax(linesDisplayed() - 1, 0));
0450 }
0451 
0452 void KateViewInternal::scrollPrevLine()
0453 {
0454     scrollViewLines(-1);
0455 }
0456 
0457 void KateViewInternal::scrollNextLine()
0458 {
0459     scrollViewLines(1);
0460 }
0461 
0462 KTextEditor::Cursor KateViewInternal::maxStartPos(bool changed)
0463 {
0464     cache()->setAcceptDirtyLayouts(true);
0465 
0466     if (m_cachedMaxStartPos.line() == -1 || changed) {
0467         KTextEditor::Cursor end(view()->textFolding().visibleLines() - 1,
0468                                 doc()->lineLength(view()->textFolding().visibleLineToLine(view()->textFolding().visibleLines() - 1)));
0469 
0470         if (view()->config()->scrollPastEnd()) {
0471             m_cachedMaxStartPos = viewLineOffset(end, -m_minLinesVisible);
0472         } else {
0473             m_cachedMaxStartPos = viewLineOffset(end, -(linesDisplayed() - 1));
0474         }
0475     }
0476 
0477     cache()->setAcceptDirtyLayouts(false);
0478 
0479     return m_cachedMaxStartPos;
0480 }
0481 
0482 // c is a virtual cursor
0483 void KateViewInternal::scrollPos(KTextEditor::Cursor &c, bool force, bool calledExternally, bool emitSignals)
0484 {
0485     if (!force && ((!view()->dynWordWrap() && c.line() == startLine()) || c == startPos())) {
0486         return;
0487     }
0488 
0489     if (c.line() < 0) {
0490         c.setLine(0);
0491     }
0492 
0493     KTextEditor::Cursor limit = maxStartPos();
0494     if (c > limit) {
0495         c = limit;
0496 
0497         // Re-check we're not just scrolling to the same place
0498         if (!force && ((!view()->dynWordWrap() && c.line() == startLine()) || c == startPos())) {
0499             return;
0500         }
0501     }
0502 
0503     int viewLinesScrolled = 0;
0504 
0505     // only calculate if this is really used and useful, could be wrong here, please recheck
0506     // for larger scrolls this makes 2-4 seconds difference on my xeon with dyn. word wrap on
0507     // try to get it really working ;)
0508     bool viewLinesScrolledUsable = !force && (c.line() >= startLine() - linesDisplayed() - 1) && (c.line() <= endLine() + linesDisplayed() + 1);
0509 
0510     if (viewLinesScrolledUsable) {
0511         viewLinesScrolled = cache()->displayViewLine(c);
0512     }
0513 
0514     m_startPos.setPosition(c);
0515 
0516     // set false here but reversed if we return to makeVisible
0517     m_madeVisible = false;
0518 
0519     if (viewLinesScrolledUsable) {
0520         int lines = linesDisplayed();
0521         if (view()->textFolding().visibleLines() < lines) {
0522             KTextEditor::Cursor end(view()->textFolding().visibleLines() - 1,
0523                                     doc()->lineLength(view()->textFolding().visibleLineToLine(view()->textFolding().visibleLines() - 1)));
0524             lines = qMin(linesDisplayed(), cache()->displayViewLine(end) + 1);
0525         }
0526 
0527         Q_ASSERT(lines >= 0);
0528 
0529         if (!calledExternally && qAbs(viewLinesScrolled) < lines &&
0530             // NOTE: on some machines we must update if the floating widget is visible
0531             //       otherwise strange painting bugs may occur during scrolling...
0532             !((view()->m_messageWidgets[KTextEditor::Message::TopInView] && view()->m_messageWidgets[KTextEditor::Message::TopInView]->isVisible())
0533               || (view()->m_messageWidgets[KTextEditor::Message::CenterInView] && view()->m_messageWidgets[KTextEditor::Message::CenterInView]->isVisible())
0534               || (view()->m_messageWidgets[KTextEditor::Message::BottomInView] && view()->m_messageWidgets[KTextEditor::Message::BottomInView]->isVisible()))) {
0535             updateView(false, viewLinesScrolled);
0536 
0537             int scrollHeight = -(viewLinesScrolled * (int)renderer()->lineHeight());
0538 
0539             // scroll excluding child widgets (floating notifications)
0540             scroll(0, scrollHeight, rect());
0541             m_leftBorder->scroll(0, scrollHeight);
0542 
0543             if (emitSignals) {
0544                 Q_EMIT view()->verticalScrollPositionChanged(m_view, c);
0545                 Q_EMIT view()->displayRangeChanged(m_view);
0546             }
0547             return;
0548         }
0549     }
0550 
0551     updateView();
0552     update();
0553     m_leftBorder->update();
0554     if (emitSignals) {
0555         Q_EMIT view()->verticalScrollPositionChanged(m_view, c);
0556         Q_EMIT view()->displayRangeChanged(m_view);
0557     }
0558 }
0559 
0560 void KateViewInternal::scrollColumns(int x)
0561 {
0562     if (x < 0) {
0563         x = 0;
0564     }
0565 
0566     if (x > m_columnScroll->maximum()) {
0567         x = m_columnScroll->maximum();
0568     }
0569 
0570     if (x == startX()) {
0571         return;
0572     }
0573 
0574     int dx = startX() - x;
0575     m_startX = x;
0576 
0577     if (qAbs(dx) < width()) {
0578         // scroll excluding child widgets (floating notifications)
0579         scroll(dx, 0, rect());
0580     } else {
0581         update();
0582     }
0583 
0584     Q_EMIT view()->horizontalScrollPositionChanged(m_view);
0585     Q_EMIT view()->displayRangeChanged(m_view);
0586 
0587     bool blocked = m_columnScroll->blockSignals(true);
0588     m_columnScroll->setValue(startX());
0589     m_columnScroll->blockSignals(blocked);
0590 }
0591 
0592 // If changed is true, the lines that have been set dirty have been updated.
0593 void KateViewInternal::updateView(bool changed, int viewLinesScrolled)
0594 {
0595     if (!isVisible() && !viewLinesScrolled && !changed) {
0596         return; // When this view is not visible, don't do anything
0597     }
0598 
0599     view()->doc()->delayAutoReload(); // Don't reload while user scrolls around
0600     bool blocked = m_lineScroll->blockSignals(true);
0601 
0602     int wrapWidth = width();
0603     if (view()->config()->dynWrapAtStaticMarker() && view()->config()->dynWordWrap()) {
0604         // We need to transform char count to a pixel width, stolen from PrintPainter::updateCache()
0605         QString s;
0606         s.fill(QLatin1Char('5'), view()->doc()->config()->wordWrapAt());
0607         wrapWidth = qMin(width(), static_cast<int>(renderer()->currentFontMetrics().boundingRect(s).width()));
0608     }
0609 
0610     if (wrapWidth != cache()->viewWidth()) {
0611         cache()->setViewWidth(wrapWidth);
0612         changed = true;
0613     }
0614 
0615     /* It was observed that height() could be negative here --
0616        when the main Kate view has 0 as size (during creation),
0617        and there frame around KateViewInternal.  In which
0618        case we'd set the view cache to 0 (or less!) lines, and
0619        start allocating huge chunks of data, later. */
0620     int newSize = (qMax(0, height()) / renderer()->lineHeight()) + 1;
0621     cache()->updateViewCache(startPos(), newSize, viewLinesScrolled);
0622     m_visibleLineCount = newSize;
0623 
0624     KTextEditor::Cursor maxStart = maxStartPos(changed);
0625     int maxLineScrollRange = maxStart.line();
0626     if (view()->dynWordWrap() && maxStart.column() != 0) {
0627         maxLineScrollRange++;
0628     }
0629     m_lineScroll->setRange(0, maxLineScrollRange);
0630 
0631     m_lineScroll->setValue(startLine());
0632     m_lineScroll->setSingleStep(1);
0633     m_lineScroll->setPageStep(qMax(0, height()) / renderer()->lineHeight());
0634     m_lineScroll->blockSignals(blocked);
0635 
0636     KateViewConfig::ScrollbarMode show_scrollbars = static_cast<KateViewConfig::ScrollbarMode>(view()->config()->showScrollbars());
0637 
0638     bool visible = ((show_scrollbars == KateViewConfig::AlwaysOn) || ((show_scrollbars == KateViewConfig::ShowWhenNeeded) && (maxLineScrollRange != 0)));
0639     bool visible_dummy = visible;
0640 
0641     m_lineScroll->setVisible(visible);
0642 
0643     if (!view()->dynWordWrap()) {
0644         int max = maxLen(startLine()) - width();
0645         if (max < 0) {
0646             max = 0;
0647         }
0648 
0649         // if we lose the ability to scroll horizontally, move view to the far-left
0650         if (max == 0) {
0651             scrollColumns(0);
0652         }
0653 
0654         blocked = m_columnScroll->blockSignals(true);
0655 
0656         // disable scrollbar
0657         m_columnScroll->setDisabled(max == 0);
0658 
0659         visible = ((show_scrollbars == KateViewConfig::AlwaysOn) || ((show_scrollbars == KateViewConfig::ShowWhenNeeded) && (max != 0)));
0660         visible_dummy &= visible;
0661         m_columnScroll->setVisible(visible);
0662 
0663         m_columnScroll->setRange(0, max + (renderer()->spaceWidth() / 2)); // Add some space for the caret at EOL
0664 
0665         m_columnScroll->setValue(startX());
0666 
0667         // Approximate linescroll
0668         m_columnScroll->setSingleStep(renderer()->currentFontMetrics().horizontalAdvance(QLatin1Char('a')));
0669         m_columnScroll->setPageStep(width());
0670 
0671         m_columnScroll->blockSignals(blocked);
0672     } else {
0673         visible_dummy = false;
0674     }
0675 
0676     m_dummy->setVisible(visible_dummy);
0677 
0678     if (changed) {
0679         updateDirty();
0680     }
0681 }
0682 
0683 /**
0684  * this function ensures a certain location is visible on the screen.
0685  * if endCol is -1, ignore making the columns visible.
0686  */
0687 void KateViewInternal::makeVisible(const KTextEditor::Cursor c, int endCol, bool force, bool center, bool calledExternally)
0688 {
0689     // qCDebug(LOG_KTE) << "MakeVisible start " << startPos() << " end " << endPos() << " -> request: " << c;// , new start [" << scroll.line << "," <<
0690     // scroll.col << "] lines " << (linesDisplayed() - 1) << " height " << height(); if the line is in a folded region, unfold all the way up if (
0691     // doc()->foldingTree()->findNodeForLine( c.line )->visible )
0692     //  qCDebug(LOG_KTE)<<"line ("<<c.line<<") should be visible";
0693 
0694     const int lnDisp = linesDisplayed();
0695     const int viewLine = cache()->displayViewLine(c, true);
0696     const bool curBelowScreen = (viewLine == -2);
0697 
0698     if (force) {
0699         KTextEditor::Cursor scroll = c;
0700         scrollPos(scroll, force, calledExternally);
0701     } else if (center && (c < startPos() || c > endPos())) {
0702         KTextEditor::Cursor scroll = viewLineOffset(c, -int(lnDisp) / 2);
0703         scrollPos(scroll, false, calledExternally);
0704     } else if ((viewLine >= (lnDisp - m_minLinesVisible)) || (curBelowScreen)) {
0705         KTextEditor::Cursor scroll = viewLineOffset(c, -(lnDisp - m_minLinesVisible - 1));
0706         scrollPos(scroll, false, calledExternally);
0707     } else if (c < viewLineOffset(startPos(), m_minLinesVisible)) {
0708         KTextEditor::Cursor scroll = viewLineOffset(c, -m_minLinesVisible);
0709         scrollPos(scroll, false, calledExternally);
0710     } else {
0711         // Check to see that we're not showing blank lines
0712         KTextEditor::Cursor max = maxStartPos();
0713         if (startPos() > max) {
0714             scrollPos(max, max.column(), calledExternally);
0715         }
0716     }
0717 
0718     if (!view()->dynWordWrap() && (endCol != -1 || view()->wrapCursor())) {
0719         KTextEditor::Cursor rc = toRealCursor(c);
0720         int sX = renderer()->cursorToX(cache()->textLayout(rc), rc, !view()->wrapCursor());
0721 
0722         int sXborder = sX - 8;
0723         if (sXborder < 0) {
0724             sXborder = 0;
0725         }
0726 
0727         if (sX < startX()) {
0728             scrollColumns(sXborder);
0729         } else if (sX > startX() + width()) {
0730             scrollColumns(sX - width() + 8);
0731         }
0732     }
0733 
0734     m_madeVisible = !force;
0735 
0736 #ifndef QT_NO_ACCESSIBILITY
0737     // FIXME -- is this needed?
0738 //    QAccessible::updateAccessibility(this, KateCursorAccessible::ChildId, QAccessible::Focus);
0739 #endif
0740 }
0741 
0742 void KateViewInternal::slotRegionVisibilityChanged()
0743 {
0744     qCDebug(LOG_KTE);
0745 
0746     // ensure the layout cache is ok for the updateCursor calls below
0747     // without the updateView() the view will jump to the bottom on hiding blocks after
0748     // change cfb0af25bdfac0d8f86b42db0b34a6bc9f9a361e
0749     cache()->clear();
0750     updateView();
0751 
0752     m_cachedMaxStartPos.setLine(-1);
0753     KTextEditor::Cursor max = maxStartPos();
0754     if (startPos() > max) {
0755         scrollPos(max, false, false, false /* don't emit signals! */);
0756     }
0757 
0758     // if text was folded: make sure the cursor is on a visible line
0759     qint64 foldedRangeId = -1;
0760     if (!view()->textFolding().isLineVisible(m_cursor.line(), &foldedRangeId)) {
0761         KTextEditor::Range foldingRange = view()->textFolding().foldingRange(foldedRangeId);
0762         Q_ASSERT(foldingRange.start().isValid());
0763 
0764         // set cursor to start of folding region
0765         updateCursor(foldingRange.start(), true);
0766     } else {
0767         // force an update of the cursor, since otherwise the m_displayCursor
0768         // line may be below the total amount of visible lines.
0769         updateCursor(m_cursor, true);
0770     }
0771 
0772     updateView();
0773     update();
0774     m_leftBorder->update();
0775 
0776     // emit signals here, scrollPos has this disabled, to ensure we do this after all stuff is updated!
0777     Q_EMIT view()->verticalScrollPositionChanged(m_view, max);
0778     Q_EMIT view()->displayRangeChanged(m_view);
0779 }
0780 
0781 void KateViewInternal::slotRegionBeginEndAddedRemoved(unsigned int)
0782 {
0783     qCDebug(LOG_KTE);
0784     // FIXME: performance problem
0785     m_leftBorder->update();
0786 }
0787 
0788 void KateViewInternal::showEvent(QShowEvent *e)
0789 {
0790     updateView();
0791 
0792     QWidget::showEvent(e);
0793 }
0794 
0795 KTextEditor::Attribute::Ptr KateViewInternal::attributeAt(const KTextEditor::Cursor position) const
0796 {
0797     KTextEditor::Attribute::Ptr attrib(new KTextEditor::Attribute());
0798     Kate::TextLine kateLine = doc()->kateTextLine(position.line());
0799     *attrib = *m_view->renderer()->attribute(kateLine.attribute(position.column()));
0800     return attrib;
0801 }
0802 
0803 int KateViewInternal::linesDisplayed() const
0804 {
0805     int h = height();
0806 
0807     // catch zero heights, even if should not happen
0808     int fh = qMax(1, renderer()->lineHeight());
0809 
0810     // default to 1, there is always one line around....
0811     // too many places calc with linesDisplayed() - 1
0812     return qMax(1, (h - (h % fh)) / fh);
0813 }
0814 
0815 QPoint KateViewInternal::cursorToCoordinate(const KTextEditor::Cursor cursor, bool realCursor, bool includeBorder) const
0816 {
0817     if (cursor.line() >= doc()->lines()) {
0818         return QPoint(-1, -1);
0819     }
0820 
0821     int viewLine = cache()->displayViewLine(realCursor ? toVirtualCursor(cursor) : cursor, true);
0822 
0823     if (viewLine < 0 || viewLine >= cache()->viewCacheLineCount()) {
0824         return QPoint(-1, -1);
0825     }
0826 
0827     const int y = (int)viewLine * renderer()->lineHeight();
0828 
0829     KateTextLayout layout = cache()->viewLine(viewLine);
0830 
0831     const auto textLength = doc()->lineLength(cursor.line());
0832     if (cursor.column() > textLength) {
0833         return QPoint(-1, -1);
0834     }
0835 
0836     int x = 0;
0837 
0838     // only set x value if we have a valid layout (bug #171027)
0839     if (layout.isValid()) {
0840         if (!layout.isRightToLeft() || (layout.isRightToLeft() && view()->dynWordWrap())) {
0841             x = (int)layout.lineLayout().cursorToX(cursor.column());
0842         } else /* rtl + dynWordWrap == false */ {
0843             // if text is rtl and dynamic wrap is false, the x offsets are in the opposite
0844             // direction i.e., [0] == biggest offset, [1] = next
0845             x = (int)layout.lineLayout().cursorToX(textLength - cursor.column());
0846         }
0847     }
0848     //  else
0849     //    qCDebug(LOG_KTE) << "Invalid Layout";
0850 
0851     if (includeBorder) {
0852         x += m_leftBorder->width();
0853     }
0854 
0855     x -= startX();
0856 
0857     return QPoint(x, y);
0858 }
0859 
0860 QPoint KateViewInternal::cursorCoordinates(bool includeBorder) const
0861 {
0862     return cursorToCoordinate(m_displayCursor, false, includeBorder);
0863 }
0864 
0865 KTextEditor::Cursor KateViewInternal::findMatchingBracket()
0866 {
0867     KTextEditor::Cursor c;
0868 
0869     if (!m_bm->toRange().isValid()) {
0870         return KTextEditor::Cursor::invalid();
0871     }
0872 
0873     Q_ASSERT(m_bmEnd->toRange().isValid());
0874     Q_ASSERT(m_bmStart->toRange().isValid());
0875 
0876     // For e.g. the text "{|}" (where | is the cursor), m_bmStart is equal to [ (0, 0)  ->  (0, 1) ]
0877     // and the closing bracket is in (0, 1). Thus, we check m_bmEnd first.
0878     if (m_bmEnd->toRange().contains(m_cursor) || m_bmEnd->end() == m_cursor.toCursor()) {
0879         c = m_bmStart->start();
0880     } else if (m_bmStart->toRange().contains(m_cursor) || m_bmStart->end() == m_cursor.toCursor()) {
0881         c = m_bmEnd->end();
0882         // We need to adjust the cursor position in case of override mode, BUG-402594
0883         if (doc()->config()->ovr()) {
0884             c.setColumn(c.column() - 1);
0885         }
0886     } else {
0887         // should never happen: a range exists, but the cursor position is
0888         // neither at the start nor at the end...
0889         return KTextEditor::Cursor::invalid();
0890     }
0891 
0892     return c;
0893 }
0894 
0895 class CalculatingCursor
0896 {
0897 public:
0898     // These constructors constrain their arguments to valid positions
0899     // before only the third one did, but that leads to crashes
0900     // see bug 227449
0901     CalculatingCursor(KateViewInternal *vi)
0902         : m_vi(vi)
0903     {
0904         makeValid();
0905     }
0906 
0907     CalculatingCursor(KateViewInternal *vi, const KTextEditor::Cursor c)
0908         : m_cursor(c)
0909         , m_vi(vi)
0910     {
0911         makeValid();
0912     }
0913 
0914     CalculatingCursor(KateViewInternal *vi, int line, int col)
0915         : m_cursor(line, col)
0916         , m_vi(vi)
0917     {
0918         makeValid();
0919     }
0920 
0921     virtual ~CalculatingCursor()
0922     {
0923     }
0924 
0925     int line() const
0926     {
0927         return m_cursor.line();
0928     }
0929 
0930     int column() const
0931     {
0932         return m_cursor.column();
0933     }
0934 
0935     operator KTextEditor::Cursor() const
0936     {
0937         return m_cursor;
0938     }
0939 
0940     virtual CalculatingCursor &operator+=(int n) = 0;
0941 
0942     virtual CalculatingCursor &operator-=(int n) = 0;
0943 
0944     CalculatingCursor &operator++()
0945     {
0946         return operator+=(1);
0947     }
0948 
0949     CalculatingCursor &operator--()
0950     {
0951         return operator-=(1);
0952     }
0953 
0954     void makeValid()
0955     {
0956         m_cursor.setLine(qBound(0, line(), int(doc()->lines() - 1)));
0957         if (view()->wrapCursor()) {
0958             m_cursor.setColumn(qBound(0, column(), doc()->lineLength(line())));
0959         } else {
0960             m_cursor.setColumn(qMax(0, column()));
0961         }
0962         Q_ASSERT(valid());
0963     }
0964 
0965     void toEdge(KateViewInternal::Bias bias)
0966     {
0967         if (bias == KateViewInternal::left) {
0968             m_cursor.setColumn(0);
0969         } else if (bias == KateViewInternal::right) {
0970             m_cursor.setColumn(doc()->lineLength(line()));
0971         }
0972     }
0973 
0974     bool atEdge() const
0975     {
0976         return atEdge(KateViewInternal::left) || atEdge(KateViewInternal::right);
0977     }
0978 
0979     bool atEdge(KateViewInternal::Bias bias) const
0980     {
0981         switch (bias) {
0982         case KateViewInternal::left:
0983             return column() == 0;
0984         case KateViewInternal::none:
0985             return atEdge();
0986         case KateViewInternal::right:
0987             return column() >= doc()->lineLength(line());
0988         default:
0989             Q_ASSERT(false);
0990             return false;
0991         }
0992     }
0993 
0994 protected:
0995     bool valid() const
0996     {
0997         return line() >= 0 && line() < doc()->lines() && column() >= 0 && (!view()->wrapCursor() || column() <= doc()->lineLength(line()));
0998     }
0999     KTextEditor::ViewPrivate *view()
1000     {
1001         return m_vi->m_view;
1002     }
1003     const KTextEditor::ViewPrivate *view() const
1004     {
1005         return m_vi->m_view;
1006     }
1007     KTextEditor::DocumentPrivate *doc()
1008     {
1009         return view()->doc();
1010     }
1011     const KTextEditor::DocumentPrivate *doc() const
1012     {
1013         return view()->doc();
1014     }
1015     KTextEditor::Cursor m_cursor;
1016     KateViewInternal *m_vi;
1017 };
1018 
1019 class BoundedCursor final : public CalculatingCursor
1020 {
1021 public:
1022     BoundedCursor(KateViewInternal *vi)
1023         : CalculatingCursor(vi)
1024     {
1025     }
1026     BoundedCursor(KateViewInternal *vi, const KTextEditor::Cursor c)
1027         : CalculatingCursor(vi, c)
1028     {
1029     }
1030     BoundedCursor(KateViewInternal *vi, int line, int col)
1031         : CalculatingCursor(vi, line, col)
1032     {
1033     }
1034     CalculatingCursor &operator+=(int n) override
1035     {
1036         KateLineLayout *thisLine = m_vi->cache()->line(line());
1037         if (!thisLine || !thisLine->isValid()) {
1038             qCWarning(LOG_KTE) << "Did not retrieve valid layout for line " << line();
1039             return *this;
1040         }
1041 
1042         const bool wrapCursor = view()->wrapCursor();
1043         int maxColumn = -1;
1044         if (n >= 0) {
1045             for (int i = 0; i < n; i++) {
1046                 if (column() >= thisLine->length()) {
1047                     if (wrapCursor) {
1048                         break;
1049 
1050                     } else if (view()->dynWordWrap()) {
1051                         // Don't go past the edge of the screen in dynamic wrapping mode
1052                         if (maxColumn == -1) {
1053                             maxColumn = thisLine->length() + ((m_vi->width() - thisLine->widthOfLastLine()) / m_vi->renderer()->spaceWidth()) - 1;
1054                         }
1055 
1056                         if (column() >= maxColumn) {
1057                             m_cursor.setColumn(maxColumn);
1058                             break;
1059                         }
1060 
1061                         m_cursor.setColumn(column() + 1);
1062 
1063                     } else {
1064                         m_cursor.setColumn(column() + 1);
1065                     }
1066 
1067                 } else {
1068                     m_cursor.setColumn(thisLine->layout()->nextCursorPosition(column()));
1069                 }
1070             }
1071         } else {
1072             for (int i = 0; i > n; i--) {
1073                 if (column() >= thisLine->length()) {
1074                     m_cursor.setColumn(column() - 1);
1075                 } else if (column() == 0) {
1076                     break;
1077                 } else {
1078                     m_cursor.setColumn(thisLine->layout()->previousCursorPosition(column()));
1079                 }
1080             }
1081         }
1082 
1083         Q_ASSERT(valid());
1084         return *this;
1085     }
1086     CalculatingCursor &operator-=(int n) override
1087     {
1088         return operator+=(-n);
1089     }
1090 };
1091 
1092 class WrappingCursor final : public CalculatingCursor
1093 {
1094 public:
1095     WrappingCursor(KateViewInternal *vi)
1096         : CalculatingCursor(vi)
1097     {
1098     }
1099     WrappingCursor(KateViewInternal *vi, const KTextEditor::Cursor c)
1100         : CalculatingCursor(vi, c)
1101     {
1102     }
1103     WrappingCursor(KateViewInternal *vi, int line, int col)
1104         : CalculatingCursor(vi, line, col)
1105     {
1106     }
1107 
1108     CalculatingCursor &operator+=(int n) override
1109     {
1110         KateLineLayout *thisLine = m_vi->cache()->line(line());
1111         if (!thisLine || !thisLine->isValid()) {
1112             qCWarning(LOG_KTE) << "Did not retrieve a valid layout for line " << line();
1113             return *this;
1114         }
1115 
1116         if (n >= 0) {
1117             for (int i = 0; i < n; i++) {
1118                 if (column() >= thisLine->length()) {
1119                     // Have come to the end of a line
1120                     if (line() >= doc()->lines() - 1)
1121                     // Have come to the end of the document
1122                     {
1123                         break;
1124                     }
1125 
1126                     // Advance to the beginning of the next line
1127                     m_cursor.setColumn(0);
1128                     m_cursor.setLine(line() + 1);
1129 
1130                     // Retrieve the next text range
1131                     thisLine = m_vi->cache()->line(line());
1132                     if (!thisLine || !thisLine->isValid()) {
1133                         qCWarning(LOG_KTE) << "Did not retrieve a valid layout for line " << line();
1134                         return *this;
1135                     }
1136 
1137                     continue;
1138                 }
1139 
1140                 m_cursor.setColumn(thisLine->layout()->nextCursorPosition(column()));
1141             }
1142 
1143         } else {
1144             for (int i = 0; i > n; i--) {
1145                 if (column() == 0) {
1146                     // Have come to the start of the document
1147                     if (line() == 0) {
1148                         break;
1149                     }
1150 
1151                     // Start going back to the end of the last line
1152                     m_cursor.setLine(line() - 1);
1153 
1154                     // Retrieve the next text range
1155                     thisLine = m_vi->cache()->line(line());
1156                     if (!thisLine || !thisLine->isValid()) {
1157                         qCWarning(LOG_KTE) << "Did not retrieve a valid layout for line " << line();
1158                         return *this;
1159                     }
1160 
1161                     // Finish going back to the end of the last line
1162                     m_cursor.setColumn(thisLine->length());
1163 
1164                     continue;
1165                 }
1166 
1167                 if (column() > thisLine->length()) {
1168                     m_cursor.setColumn(column() - 1);
1169                 } else {
1170                     m_cursor.setColumn(thisLine->layout()->previousCursorPosition(column()));
1171                 }
1172             }
1173         }
1174 
1175         Q_ASSERT(valid());
1176         return *this;
1177     }
1178     CalculatingCursor &operator-=(int n) override
1179     {
1180         return operator+=(-n);
1181     }
1182 };
1183 
1184 /**
1185  * @brief The CamelCursor class
1186  *
1187  * This class allows for "camel humps" when moving the cursor
1188  * using Ctrl + Left / Right. Similarly, this will also get triggered
1189  * when you press Ctrl+Shift+Left/Right for selection and Ctrl+Del
1190  * Ctrl + backspace for deletion.
1191  *
1192  * It is absoloutely essential that if you move through a word in 'n'
1193  * jumps, you should be able to move back with exactly same 'n' movements
1194  * which you made when moving forward. Example:
1195  *
1196  * Word: KateViewInternal
1197  *
1198  * Moving cursor towards right while holding control will result in cursor
1199  * landing in the following places (assuming you start from column 0)
1200  *
1201  *     |   |       |
1202  * KateViewInternal
1203  *
1204  * Moving the cursor back to get to the starting position should also
1205  * take exactly 3 movements:
1206  *
1207  * |   |   |
1208  * KateViewInternal
1209  *
1210  * In addition to simple camel case, this class also handles snake_case
1211  * capitalized snake, and mixtures of Camel + snake/underscore, for example
1212  * m_someMember. If a word has underscores in it, for example:
1213  *
1214  * snake_case_word
1215  *
1216  * the leading underscore is considered part of the word and thus a cursor
1217  * movement will land right after the underscore. Moving the cursor to end
1218  * for the above word should be like this:
1219  *
1220  * startpos: 0
1221  *       |    |   |
1222  * snake_case_word
1223  *
1224  * When jumping back to startpos, exact same "spots" need to be hit on your way
1225  * back.
1226  *
1227  * If a word has multiple leading underscores: snake___case, the underscores will
1228  * be considered part of the word and thus a jump wil land us "after" the last underscore.
1229  *
1230  * There are some other variations in this, for example Capitalized words, or identifiers
1231  * with numbers in betweens. For such cases, All capitalized words are skipped in one go
1232  * till we are on some "non-capitalized" word. In this context, a number is a non-capitalized
1233  * word so will break the jump. Examples:
1234  *
1235  *   | |
1236  * W1RD
1237  *
1238  * but for WORD_CAPITAL, following will happen:
1239  *
1240  *      |      |
1241  * WORD_CAPITAL
1242  *
1243  * The first case here is tricky to handle for reverse movement. I haven't noticed any
1244  * cases which the current implementation is unable to handle but there might be some.
1245  *
1246  * With languages like PHP where you have $ as part of the identifier, the cursor jump
1247  * will break "after" dollar. Consider: $phpVar, Following will happen:
1248  *
1249  *  |  |  |
1250  * $phpVar
1251  *
1252  * And of course, the reverse will be exact opposite.
1253  *
1254  * Similar to PHP, with CSS Colors, jump will break on '#' charachter
1255  *
1256  * Please see the test cases testWordMovementSingleRow() for more examples/data.
1257  *
1258  * It is absoloutely essential to know that this class *only* gets triggered for
1259  * cursor movement if we are in a word.
1260  *
1261  * Note to bugfixer: If some bug occurs, before changing anything please add a test
1262  * case for the bug and make sure everything passes before and after. The test case
1263  * for this can be found autotests/src/camelcursortest.cpp.
1264  *
1265  * @author Waqar Ahmed <waqar.17a@gmail.com>
1266  */
1267 class CamelCursor final : public CalculatingCursor
1268 {
1269 public:
1270     CamelCursor(KateViewInternal *vi, const KTextEditor::Cursor c)
1271         : CalculatingCursor(vi, c)
1272     {
1273     }
1274 
1275     CalculatingCursor &operator+=(int n) override
1276     {
1277         KateLineLayout *thisLine = m_vi->cache()->line(line());
1278         if (!thisLine || !thisLine->isValid()) {
1279             qCWarning(LOG_KTE) << "Did not retrieve valid layout for line " << line();
1280             return *this;
1281         }
1282 
1283         auto isSurrogate = [](QChar c) {
1284             return c.isLowSurrogate() || c.isHighSurrogate();
1285         };
1286 
1287         if (n >= 0) {
1288             auto skipCaps = [](QStringView text, int &col) {
1289                 int count = 0;
1290                 while (col < text.size() && text.at(col).isUpper()) {
1291                     ++count;
1292                     ++col;
1293                 }
1294                 // if this is a letter, then it means we are in the
1295                 // middle of a word, step back one position so that
1296                 // we are at the last Cap letter
1297                 // Otherwise, it's an all cap word
1298                 if (count > 1 && col < text.size() && text.at(col).isLetterOrNumber()) {
1299                     --col;
1300                 }
1301             };
1302 
1303             int jump = -1;
1304             int col = column();
1305             const QString &text = thisLine->textLine().text();
1306 
1307             if (col < text.size() && text.at(col).isUpper()) {
1308                 skipCaps(text, col);
1309             }
1310 
1311             for (int i = col; i < thisLine->length(); ++i) {
1312                 const auto c = text.at(i);
1313                 if (isSurrogate(c)) {
1314                     col++;
1315                     continue;
1316                 } else if (c.isUpper() || !c.isLetterOrNumber()) {
1317                     break;
1318                 }
1319                 ++col;
1320             }
1321 
1322             // eat any '_' that are after the word BEFORE any space happens
1323             if (col < text.size() && text.at(col) == QLatin1Char('_')) {
1324                 while (col < text.size() && text.at(col) == QLatin1Char('_')) {
1325                     ++col;
1326                 }
1327             }
1328 
1329             // Underscores eaten, so now eat any spaces till next word
1330             if (col < text.size() && text.at(col).isSpace()) {
1331                 while (col < text.size() && text.at(col).isSpace()) {
1332                     ++col;
1333                 }
1334             }
1335 
1336             jump = col < 0 || (column() == col) ? (column() + 1) : col;
1337             m_cursor.setColumn(jump);
1338         } else {
1339             int jump = -1;
1340 
1341             auto skipCapsRev = [](QStringView text, int &col) {
1342                 int count = 0;
1343                 while (col > 0 && text.at(col).isUpper()) {
1344                     ++count;
1345                     --col;
1346                 }
1347 
1348                 // if more than one cap found, and current
1349                 // column is not upper, we want to move ahead
1350                 // to the upper
1351                 if (count >= 1 && col >= 0 && !text.at(col).isUpper()) {
1352                     ++col;
1353                 }
1354             };
1355 
1356             const QString &text = thisLine->textLine().text();
1357             int col = std::min<int>(column(), text.size() - 1);
1358             // decrement because we might be standing at a camel hump
1359             // already and don't want to return the same position
1360             col = col - 1;
1361 
1362             // if we are at the end of line
1363             if (column() == text.size()) {
1364                 // there is a letter before
1365                 if (text.at(col + 1).isLetter()) {
1366                     // and a space before that
1367                     if (col >= 0 && text.at(col).isSpace()) {
1368                         // we have a one letter word,
1369                         // so move forward to end of word
1370                         ++col;
1371                     }
1372                 }
1373             }
1374 
1375             // skip any spaces
1376             if (col > 0 && text.at(col).isSpace()) {
1377                 while (text.at(col).isSpace() && col > 0) {
1378                     --col;
1379                 }
1380             }
1381 
1382             // Skip Underscores
1383             if (col > 0 && text.at(col) == QLatin1Char('_')) {
1384                 while (col > 0 && text.at(col) == QLatin1Char('_')) {
1385                     --col;
1386                 }
1387             }
1388 
1389             if (col > 0 && text.at(col).isUpper()) {
1390                 skipCapsRev(text, col);
1391             }
1392 
1393             for (int i = col; i > 0; --i) {
1394                 const auto c = text.at(i);
1395                 if (isSurrogate(c)) {
1396                     --col;
1397                     continue;
1398                 } else if (c.isUpper() || !c.isLetterOrNumber()) {
1399                     break;
1400                 }
1401                 --col;
1402             }
1403 
1404             if (col >= 0 && !text.at(col).isLetterOrNumber() && !isSurrogate(text.at(col))) {
1405                 ++col;
1406             }
1407 
1408             if (col < 0) {
1409                 jump = 0;
1410             } else if (col == column() && column() > 0) {
1411                 jump = column() - 1;
1412             } else {
1413                 jump = col;
1414             }
1415 
1416             m_cursor.setColumn(jump);
1417         }
1418 
1419         Q_ASSERT(valid());
1420         return *this;
1421     }
1422 
1423     CalculatingCursor &operator-=(int n) override
1424     {
1425         return operator+=(-n);
1426     }
1427 };
1428 
1429 void KateViewInternal::moveChar(KateViewInternal::Bias bias, bool sel)
1430 {
1431     KTextEditor::Cursor c;
1432     if (view()->wrapCursor()) {
1433         c = WrappingCursor(this, m_cursor) += bias;
1434     } else {
1435         c = BoundedCursor(this, m_cursor) += bias;
1436     }
1437 
1438     const auto &sc = view()->m_secondaryCursors;
1439     QVarLengthArray<CursorPair, 16> multiCursors;
1440     const int lastLine = doc()->lastLine();
1441     bool shouldEnsureUniqueCursors = false;
1442     for (const auto &c : sc) {
1443         auto oldPos = c.cursor();
1444         if (view()->wrapCursor()) {
1445             c.pos->setPosition(WrappingCursor(this, oldPos) += bias);
1446         } else {
1447             c.pos->setPosition(BoundedCursor(this, oldPos) += bias);
1448         }
1449         const auto newPos = c.pos->toCursor();
1450         multiCursors.push_back({oldPos, newPos});
1451         // We only need to do this if cursors were in first or last line
1452         if (!shouldEnsureUniqueCursors) {
1453             shouldEnsureUniqueCursors = newPos.line() == 0 || newPos.line() == lastLine;
1454         }
1455     }
1456 
1457     updateSelection(c, sel);
1458     updateCursor(c);
1459     updateSecondaryCursors(multiCursors, sel);
1460     if (shouldEnsureUniqueCursors) {
1461         view()->ensureUniqueCursors();
1462     }
1463 }
1464 
1465 void KateViewInternal::cursorPrevChar(bool sel)
1466 {
1467     if (!view()->wrapCursor() && m_cursor.column() == 0) {
1468         return;
1469     }
1470 
1471     moveChar(KateViewInternal::left, sel);
1472 }
1473 
1474 void KateViewInternal::cursorNextChar(bool sel)
1475 {
1476     moveChar(KateViewInternal::right, sel);
1477 }
1478 
1479 void KateViewInternal::wordPrev(bool sel)
1480 {
1481     auto characterAtPreviousColumn = [this](KTextEditor::Cursor cursor) -> QChar {
1482         return doc()->characterAt({cursor.line(), cursor.column() - 1});
1483     };
1484 
1485     auto wordPrevious = [this, &characterAtPreviousColumn](KTextEditor::Cursor cursor) -> KTextEditor::Cursor {
1486         WrappingCursor c(this, cursor);
1487 
1488         // First we skip backwards all space.
1489         // Then we look up into which category the current position falls:
1490         // 1. a "word" character
1491         // 2. a "non-word" character (except space)
1492         // 3. the beginning of the line
1493         // and skip all preceding characters that fall into this class.
1494         // The code assumes that space is never part of the word character class.
1495 
1496         KateHighlighting *h = doc()->highlight();
1497 
1498         while (!c.atEdge(left) && (c.column() > doc()->lineLength(c.line()) || characterAtPreviousColumn(c).isSpace())) {
1499             --c;
1500         }
1501 
1502         if (c.atEdge(left)) {
1503             --c;
1504         } else if (h->isInWord(characterAtPreviousColumn(c))) {
1505             if (doc()->config()->camelCursor()) {
1506                 CamelCursor cc(this, cursor);
1507                 --cc;
1508                 return cc;
1509             } else {
1510                 while (!c.atEdge(left) && h->isInWord(characterAtPreviousColumn(c))) {
1511                     --c;
1512                 }
1513             }
1514         } else {
1515             while (!c.atEdge(left)
1516                    && !h->isInWord(characterAtPreviousColumn(c))
1517                    // in order to stay symmetric to wordLeft()
1518                    // we must not skip space preceding a non-word sequence
1519                    && !characterAtPreviousColumn(c).isSpace()) {
1520                 --c;
1521             }
1522         }
1523 
1524         return c;
1525     };
1526 
1527     const auto &secondaryCursors = view()->m_secondaryCursors;
1528     QVarLengthArray<CursorPair, 16> cursorsToUpdate;
1529     for (const auto &cursor : secondaryCursors) {
1530         auto oldPos = cursor.cursor();
1531         auto newCursorPos = wordPrevious(cursor.cursor());
1532         cursor.pos->setPosition(newCursorPos);
1533         cursorsToUpdate.push_back({oldPos, newCursorPos});
1534     }
1535 
1536     // update primary cursor
1537     const auto c = wordPrevious(m_cursor);
1538     updateSelection(c, sel);
1539     updateCursor(c);
1540 
1541     if (!sel) {
1542         view()->ensureUniqueCursors();
1543     }
1544     updateSecondaryCursors(cursorsToUpdate, sel);
1545 }
1546 
1547 void KateViewInternal::wordNext(bool sel)
1548 {
1549     auto nextWord = [this](KTextEditor::Cursor cursor) -> KTextEditor::Cursor {
1550         WrappingCursor c(this, cursor);
1551 
1552         // We look up into which category the current position falls:
1553         // 1. a "word" character
1554         // 2. a "non-word" character (except space)
1555         // 3. the end of the line
1556         // and skip all following characters that fall into this class.
1557         // If the skipped characters are followed by space, we skip that too.
1558         // The code assumes that space is never part of the word character class.
1559 
1560         KateHighlighting *h = doc()->highlight();
1561         if (c.atEdge(right)) {
1562             ++c;
1563         } else if (h->isInWord(doc()->characterAt(c))) {
1564             if (doc()->config()->camelCursor()) {
1565                 CamelCursor cc(this, cursor);
1566                 ++cc;
1567                 return cc;
1568             } else {
1569                 while (!c.atEdge(right) && h->isInWord(doc()->characterAt(c))) {
1570                     ++c;
1571                 }
1572             }
1573         } else {
1574             while (!c.atEdge(right)
1575                    && !h->isInWord(doc()->characterAt(c))
1576                    // we must not skip space, because if that space is followed
1577                    // by more non-word characters, we would skip them, too
1578                    && !doc()->characterAt(c).isSpace()) {
1579                 ++c;
1580             }
1581         }
1582 
1583         while (!c.atEdge(right) && doc()->characterAt(c).isSpace()) {
1584             ++c;
1585         }
1586 
1587         return c;
1588     };
1589 
1590     const auto &secondaryCursors = view()->m_secondaryCursors;
1591     QVarLengthArray<CursorPair, 16> cursorsToUpdate;
1592     for (const auto &cursor : secondaryCursors) {
1593         auto oldPos = cursor.cursor();
1594         auto newCursorPos = nextWord(cursor.cursor());
1595         cursor.pos->setPosition(newCursorPos);
1596         cursorsToUpdate.push_back({oldPos, newCursorPos});
1597     }
1598 
1599     // update primary cursor
1600     const auto c = nextWord(m_cursor);
1601     updateSelection(c, sel);
1602     updateCursor(c);
1603 
1604     // Remove cursors which have same position
1605     if (!sel) {
1606         view()->ensureUniqueCursors();
1607     }
1608     updateSecondaryCursors(cursorsToUpdate, sel);
1609 }
1610 
1611 void KateViewInternal::moveEdge(KateViewInternal::Bias bias, bool sel)
1612 {
1613     BoundedCursor c(this, m_cursor);
1614     c.toEdge(bias);
1615     updateSelection(c, sel);
1616     updateCursor(c);
1617 }
1618 
1619 KTextEditor::Cursor KateViewInternal::moveCursorToLineStart(KTextEditor::Cursor cursor)
1620 {
1621     if (view()->dynWordWrap() && currentLayout(cursor).startCol()) {
1622         // Allow us to go to the real start if we're already at the start of the view line
1623         if (cursor.column() != currentLayout(cursor).startCol()) {
1624             KTextEditor::Cursor c = currentLayout(cursor).start();
1625             return c;
1626         }
1627     }
1628 
1629     if (!doc()->config()->smartHome()) {
1630         BoundedCursor c(this, cursor);
1631         c.toEdge(left);
1632         return c;
1633     }
1634 
1635     if (cursor.line() < 0 || cursor.line() >= doc()->lines()) {
1636         return KTextEditor::Cursor::invalid();
1637     }
1638 
1639     Kate::TextLine l = doc()->kateTextLine(cursor.line());
1640 
1641     KTextEditor::Cursor c = cursor;
1642     int lc = l.firstChar();
1643 
1644     if (lc < 0 || c.column() == lc) {
1645         c.setColumn(0);
1646     } else {
1647         c.setColumn(lc);
1648     }
1649     return c;
1650 }
1651 
1652 void KateViewInternal::home(bool sel)
1653 {
1654     // Multicursor
1655     view()->ensureUniqueCursors(/*matchLine*/ true);
1656     const auto &secondaryCursors = view()->m_secondaryCursors;
1657     QVarLengthArray<CursorPair, 16> cursorsToUpdate;
1658     for (const auto &c : secondaryCursors) {
1659         auto oldPos = c.cursor();
1660         // These will end up in same place so just remove
1661         auto newPos = moveCursorToLineStart(oldPos);
1662         c.pos->setPosition(newPos);
1663         cursorsToUpdate.push_back({oldPos, newPos});
1664     }
1665 
1666     // Primary cursor
1667     auto newPos = moveCursorToLineStart(m_cursor);
1668     if (newPos.isValid()) {
1669         updateSelection(newPos, sel);
1670         updateCursor(newPos, true);
1671     }
1672     updateSecondaryCursors(cursorsToUpdate, sel);
1673 }
1674 
1675 KTextEditor::Cursor KateViewInternal::moveCursorToLineEnd(KTextEditor::Cursor cursor)
1676 {
1677     KateTextLayout layout = currentLayout(cursor);
1678 
1679     if (view()->dynWordWrap() && layout.wrap()) {
1680         // Allow us to go to the real end if we're already at the end of the view line
1681         if (cursor.column() < layout.endCol() - 1) {
1682             KTextEditor::Cursor c(cursor.line(), layout.endCol() - 1);
1683             return c;
1684         }
1685     }
1686 
1687     if (!doc()->config()->smartHome()) {
1688         BoundedCursor c(this, cursor);
1689         c.toEdge(right);
1690         return c;
1691     }
1692 
1693     if (cursor.line() < 0 || cursor.line() >= doc()->lines()) {
1694         return KTextEditor::Cursor::invalid();
1695     }
1696 
1697     Kate::TextLine l = doc()->kateTextLine(cursor.line());
1698 
1699     // "Smart End", as requested in bugs #78258 and #106970
1700     if (cursor.column() == doc()->lineLength(cursor.line())) {
1701         KTextEditor::Cursor c = cursor;
1702         c.setColumn(l.lastChar() + 1);
1703         return c;
1704     } else {
1705         BoundedCursor c(this, cursor);
1706         c.toEdge(right);
1707         return c;
1708     }
1709 }
1710 
1711 void KateViewInternal::end(bool sel)
1712 {
1713     // Multicursor
1714     view()->ensureUniqueCursors(/*matchLine*/ true);
1715 
1716     QVarLengthArray<CursorPair, 16> cursorsToUpdate;
1717     const auto &secondaryCursors = view()->m_secondaryCursors;
1718     for (const auto &c : secondaryCursors) {
1719         auto oldPos = c.cursor();
1720         // These will end up in same place so just remove
1721         auto newPos = moveCursorToLineEnd(oldPos);
1722         c.pos->setPosition(newPos);
1723         cursorsToUpdate.push_back({oldPos, newPos});
1724     }
1725 
1726     auto newPos = moveCursorToLineEnd(m_cursor);
1727     if (newPos.isValid()) {
1728         updateSelection(newPos, sel);
1729         updateCursor(newPos);
1730     }
1731 
1732     updateSecondaryCursors(cursorsToUpdate, sel);
1733     paintCursor();
1734 }
1735 
1736 KateTextLayout KateViewInternal::currentLayout(KTextEditor::Cursor c) const
1737 {
1738     return cache()->textLayout(c);
1739 }
1740 
1741 KateTextLayout KateViewInternal::previousLayout(KTextEditor::Cursor c) const
1742 {
1743     int currentViewLine = cache()->viewLine(c);
1744 
1745     if (currentViewLine) {
1746         return cache()->textLayout(c.line(), currentViewLine - 1);
1747     } else {
1748         return cache()->textLayout(view()->textFolding().visibleLineToLine(toVirtualCursor(c).line() - 1), -1);
1749     }
1750 }
1751 
1752 KateTextLayout KateViewInternal::nextLayout(KTextEditor::Cursor c) const
1753 {
1754     int currentViewLine = cache()->viewLine(c) + 1;
1755 
1756     const KateLineLayout *thisLine = cache()->line(c.line());
1757     if (thisLine && currentViewLine >= thisLine->viewLineCount()) {
1758         currentViewLine = 0;
1759         return cache()->textLayout(view()->textFolding().visibleLineToLine(toVirtualCursor(c).line() + 1), currentViewLine);
1760     } else {
1761         return cache()->textLayout(c.line(), currentViewLine);
1762     }
1763 }
1764 
1765 /*
1766  * This returns the cursor which is offset by (offset) view lines.
1767  * This is the main function which is called by code not specifically dealing with word-wrap.
1768  * The opposite conversion (cursor to offset) can be done with cache()->displayViewLine().
1769  *
1770  * The cursors involved are virtual cursors (ie. equivalent to m_displayCursor)
1771  */
1772 
1773 KTextEditor::Cursor KateViewInternal::viewLineOffset(const KTextEditor::Cursor virtualCursor, int offset, bool keepX)
1774 {
1775     if (!view()->dynWordWrap()) {
1776         KTextEditor::Cursor ret(qMin((int)view()->textFolding().visibleLines() - 1, virtualCursor.line() + offset), 0);
1777 
1778         if (ret.line() < 0) {
1779             ret.setLine(0);
1780         }
1781 
1782         if (keepX) {
1783             int realLine = view()->textFolding().visibleLineToLine(ret.line());
1784             KateTextLayout t = cache()->textLayout(realLine, 0);
1785             Q_ASSERT(t.isValid());
1786 
1787             ret.setColumn(renderer()->xToCursor(t, m_preservedX, !view()->wrapCursor()).column());
1788         }
1789 
1790         return ret;
1791     }
1792 
1793     KTextEditor::Cursor realCursor = virtualCursor;
1794     realCursor.setLine(view()->textFolding().visibleLineToLine(view()->textFolding().lineToVisibleLine(virtualCursor.line())));
1795 
1796     int cursorViewLine = cache()->viewLine(realCursor);
1797 
1798     int currentOffset = 0;
1799     int virtualLine = 0;
1800 
1801     bool forwards = (offset > 0) ? true : false;
1802 
1803     if (forwards) {
1804         currentOffset = cache()->lastViewLine(realCursor.line()) - cursorViewLine;
1805         if (offset <= currentOffset) {
1806             // the answer is on the same line
1807             KateTextLayout thisLine = cache()->textLayout(realCursor.line(), cursorViewLine + offset);
1808             Q_ASSERT(thisLine.virtualLine() == (int)view()->textFolding().lineToVisibleLine(virtualCursor.line()));
1809             return KTextEditor::Cursor(virtualCursor.line(), thisLine.startCol());
1810         }
1811 
1812         virtualLine = virtualCursor.line() + 1;
1813 
1814     } else {
1815         offset = -offset;
1816         currentOffset = cursorViewLine;
1817         if (offset <= currentOffset) {
1818             // the answer is on the same line
1819             KateTextLayout thisLine = cache()->textLayout(realCursor.line(), cursorViewLine - offset);
1820             Q_ASSERT(thisLine.virtualLine() == (int)view()->textFolding().lineToVisibleLine(virtualCursor.line()));
1821             return KTextEditor::Cursor(virtualCursor.line(), thisLine.startCol());
1822         }
1823 
1824         virtualLine = virtualCursor.line() - 1;
1825     }
1826 
1827     currentOffset++;
1828 
1829     while (virtualLine >= 0 && virtualLine < (int)view()->textFolding().visibleLines()) {
1830         int realLine = view()->textFolding().visibleLineToLine(virtualLine);
1831         KateLineLayout *thisLine = cache()->line(realLine, virtualLine);
1832         if (!thisLine) {
1833             break;
1834         }
1835 
1836         for (int i = 0; i < thisLine->viewLineCount(); ++i) {
1837             if (offset == currentOffset) {
1838                 KateTextLayout thisViewLine = thisLine->viewLine(i);
1839 
1840                 if (!forwards) {
1841                     // We actually want it the other way around
1842                     int requiredViewLine = cache()->lastViewLine(realLine) - thisViewLine.viewLine();
1843                     if (requiredViewLine != thisViewLine.viewLine()) {
1844                         thisViewLine = thisLine->viewLine(requiredViewLine);
1845                     }
1846                 }
1847 
1848                 KTextEditor::Cursor ret(virtualLine, thisViewLine.startCol());
1849 
1850                 // keep column position
1851                 if (keepX) {
1852                     realCursor = renderer()->xToCursor(thisViewLine, m_preservedX, !view()->wrapCursor());
1853                     ret.setColumn(realCursor.column());
1854                 }
1855 
1856                 return ret;
1857             }
1858 
1859             currentOffset++;
1860         }
1861 
1862         if (forwards) {
1863             virtualLine++;
1864         } else {
1865             virtualLine--;
1866         }
1867     }
1868 
1869     // Looks like we were asked for something a bit exotic.
1870     // Return the max/min valid position.
1871     if (forwards) {
1872         return KTextEditor::Cursor(view()->textFolding().visibleLines() - 1,
1873                                    doc()->lineLength(view()->textFolding().visibleLineToLine(view()->textFolding().visibleLines() - 1)));
1874     } else {
1875         return KTextEditor::Cursor(0, 0);
1876     }
1877 }
1878 
1879 int KateViewInternal::lineMaxCursorX(const KateTextLayout &range)
1880 {
1881     if (!view()->wrapCursor() && !range.wrap()) {
1882         return INT_MAX;
1883     }
1884 
1885     int maxX = range.endX();
1886 
1887     if (maxX && range.wrap()) {
1888         QChar lastCharInLine = doc()->kateTextLine(range.line()).at(range.endCol() - 1);
1889         maxX -= renderer()->currentFontMetrics().horizontalAdvance(lastCharInLine);
1890     }
1891 
1892     return maxX;
1893 }
1894 
1895 int KateViewInternal::lineMaxCol(const KateTextLayout &range)
1896 {
1897     int maxCol = range.endCol();
1898 
1899     if (maxCol && range.wrap()) {
1900         maxCol--;
1901     }
1902 
1903     return maxCol;
1904 }
1905 
1906 void KateViewInternal::cursorUp(bool sel)
1907 {
1908     if (!sel && view()->completionWidget()->isCompletionActive()) {
1909         view()->completionWidget()->cursorUp();
1910         return;
1911     }
1912 
1913     m_preserveX = true;
1914 
1915     // Handle Multi cursors
1916     // usually this will have only one element, rarely
1917     int i = 0;
1918     for (const auto &c : view()->m_secondaryCursors) {
1919         auto cursor = c.pos->toCursor();
1920         auto vCursor = toVirtualCursor(cursor);
1921 
1922         // our cursor is in the first line already
1923         if (vCursor.line() == 0 && (!view()->dynWordWrap() || cache()->viewLine(cursor) == 0)) {
1924             auto newPos = moveCursorToLineStart(cursor);
1925             c.pos->setPosition(newPos);
1926             auto newVcursor = toVirtualCursor(newPos);
1927             if (sel) {
1928                 updateSecondarySelection(i, cursor, newPos);
1929             } else {
1930                 view()->clearSecondarySelections();
1931             }
1932             tagLines(newVcursor.line(), vCursor.line());
1933             i++;
1934             continue;
1935         }
1936 
1937         auto lineLayout = currentLayout(cursor);
1938         Q_ASSERT(lineLayout.line() == cursor.line());
1939         Q_ASSERT(lineLayout.startCol() <= cursor.column());
1940         Q_ASSERT(!lineLayout.wrap() || cursor.column() < lineLayout.endCol());
1941 
1942         KateTextLayout pRange = previousLayout(cursor);
1943 
1944         KTextEditor::Cursor newPos = renderer()->xToCursor(pRange, m_preservedX, !view()->wrapCursor());
1945         c.pos->setPosition(newPos);
1946 
1947         auto newVcursor = toVirtualCursor(newPos);
1948         if (sel) {
1949             updateSecondarySelection(i, cursor, newPos);
1950         } else {
1951             view()->clearSecondarySelections();
1952         }
1953         tagLines(newVcursor.line(), vCursor.line());
1954         i++;
1955     }
1956 
1957     auto mergeOnFuncEnd = qScopeGuard([this, sel] {
1958         if (sel) {
1959             mergeSelections();
1960         } else {
1961             view()->ensureUniqueCursors();
1962         }
1963     });
1964 
1965     // Normal single cursor
1966 
1967     // assert that the display cursor is in visible lines
1968     Q_ASSERT(m_displayCursor.line() < view()->textFolding().visibleLines());
1969 
1970     // move cursor to start of line, if we are at first line!
1971     if (m_displayCursor.line() == 0 && (!view()->dynWordWrap() || cache()->viewLine(m_cursor) == 0)) {
1972         auto newPos = moveCursorToLineStart(m_cursor);
1973         if (newPos.isValid()) {
1974             updateSelection(newPos, sel);
1975             updateCursor(newPos, true);
1976         }
1977         return;
1978     }
1979 
1980     KateTextLayout thisLine = currentLayout(m_cursor);
1981     // This is not the first line because that is already simplified out above
1982     KateTextLayout pRange = previousLayout(m_cursor);
1983 
1984     // Ensure we're in the right spot
1985     Q_ASSERT(m_cursor.line() == thisLine.line());
1986     Q_ASSERT(m_cursor.column() >= thisLine.startCol());
1987     Q_ASSERT(!thisLine.wrap() || m_cursor.column() < thisLine.endCol());
1988 
1989     KTextEditor::Cursor c = renderer()->xToCursor(pRange, m_preservedX, !view()->wrapCursor());
1990 
1991     updateSelection(c, sel);
1992     updateCursor(c);
1993 }
1994 
1995 void KateViewInternal::cursorDown(bool sel)
1996 {
1997     if (!sel && view()->completionWidget()->isCompletionActive()) {
1998         view()->completionWidget()->cursorDown();
1999         return;
2000     }
2001 
2002     m_preserveX = true;
2003 
2004     // Handle multiple cursors
2005     int i = 0;
2006     for (const auto &c : view()->m_secondaryCursors) {
2007         auto cursor = c.cursor();
2008         auto vCursor = toVirtualCursor(cursor);
2009 
2010         // at end?
2011         if ((vCursor.line() >= view()->textFolding().visibleLines() - 1)
2012             && (!view()->dynWordWrap() || cache()->viewLine(cursor) == cache()->lastViewLine(cursor.line()))) {
2013             KTextEditor::Cursor newPos = moveCursorToLineEnd(cursor);
2014             c.pos->setPosition(newPos);
2015             if (sel) {
2016                 updateSecondarySelection(i, cursor, newPos);
2017             } else {
2018                 view()->clearSecondarySelections();
2019             }
2020             auto vNewPos = toVirtualCursor(newPos);
2021             tagLines(vCursor.line(), vNewPos.line());
2022             i++;
2023             continue;
2024         }
2025 
2026         KateTextLayout thisLine = currentLayout(cursor);
2027         // This is not the last line because that is already simplified out above
2028         KateTextLayout nRange = nextLayout(cursor);
2029 
2030         // Ensure we're in the right spot
2031         Q_ASSERT((cursor.line() == thisLine.line()) && (cursor.column() >= thisLine.startCol()) && (!thisLine.wrap() || cursor.column() < thisLine.endCol()));
2032         KTextEditor::Cursor newPos = renderer()->xToCursor(nRange, m_preservedX, !view()->wrapCursor());
2033 
2034         c.pos->setPosition(newPos);
2035         if (sel) {
2036             updateSecondarySelection(i, cursor, newPos);
2037         } else {
2038             view()->clearSecondarySelections();
2039         }
2040         auto vNewPos = toVirtualCursor(newPos);
2041         tagLines(vCursor.line(), vNewPos.line());
2042         i++;
2043     }
2044     auto mergeOnFuncEnd = qScopeGuard([this, sel] {
2045         if (sel) {
2046             mergeSelections();
2047         } else {
2048             view()->ensureUniqueCursors();
2049         }
2050     });
2051 
2052     // Handle normal single cursor
2053 
2054     // move cursor to end of line, if we are at last line!
2055     if ((m_displayCursor.line() >= view()->textFolding().visibleLines() - 1)
2056         && (!view()->dynWordWrap() || cache()->viewLine(m_cursor) == cache()->lastViewLine(m_cursor.line()))) {
2057         auto newPos = moveCursorToLineEnd(m_cursor);
2058         if (newPos.isValid()) {
2059             updateSelection(newPos, sel);
2060             updateCursor(newPos);
2061         }
2062         return;
2063     }
2064 
2065     KateTextLayout thisLine = currentLayout(m_cursor);
2066     // This is not the last line because that is already simplified out above
2067     KateTextLayout nRange = nextLayout(m_cursor);
2068 
2069     // Ensure we're in the right spot
2070     Q_ASSERT((m_cursor.line() == thisLine.line()) && (m_cursor.column() >= thisLine.startCol()) && (!thisLine.wrap() || m_cursor.column() < thisLine.endCol()));
2071 
2072     KTextEditor::Cursor c = renderer()->xToCursor(nRange, m_preservedX, !view()->wrapCursor());
2073 
2074     updateSelection(c, sel);
2075     updateCursor(c);
2076 }
2077 
2078 void KateViewInternal::cursorToMatchingBracket(bool sel)
2079 {
2080     KTextEditor::Cursor c = findMatchingBracket();
2081 
2082     if (c.isValid()) {
2083         updateSelection(c, sel);
2084         updateCursor(c);
2085     }
2086 }
2087 
2088 void KateViewInternal::topOfView(bool sel)
2089 {
2090     view()->clearSecondaryCursors();
2091     KTextEditor::Cursor c = viewLineOffset(startPos(), m_minLinesVisible);
2092     updateSelection(toRealCursor(c), sel);
2093     updateCursor(toRealCursor(c));
2094 }
2095 
2096 void KateViewInternal::bottomOfView(bool sel)
2097 {
2098     view()->clearSecondaryCursors();
2099     KTextEditor::Cursor c = viewLineOffset(endPos(), -m_minLinesVisible);
2100     updateSelection(toRealCursor(c), sel);
2101     updateCursor(toRealCursor(c));
2102 }
2103 
2104 // lines is the offset to scroll by
2105 void KateViewInternal::scrollLines(int lines, bool sel)
2106 {
2107     KTextEditor::Cursor c = viewLineOffset(m_displayCursor, lines, true);
2108 
2109     // Fix the virtual cursor -> real cursor
2110     c.setLine(view()->textFolding().visibleLineToLine(c.line()));
2111 
2112     updateSelection(c, sel);
2113     updateCursor(c);
2114 }
2115 
2116 // This is a bit misleading... it's asking for the view to be scrolled, not the cursor
2117 void KateViewInternal::scrollUp()
2118 {
2119     KTextEditor::Cursor newPos = viewLineOffset(startPos(), -1);
2120     scrollPos(newPos);
2121 }
2122 
2123 void KateViewInternal::scrollDown()
2124 {
2125     KTextEditor::Cursor newPos = viewLineOffset(startPos(), 1);
2126     scrollPos(newPos);
2127 }
2128 
2129 void KateViewInternal::setAutoCenterLines(int viewLines, bool updateView)
2130 {
2131     m_autoCenterLines = viewLines;
2132     m_minLinesVisible = qMin(int((linesDisplayed() - 1) / 2), m_autoCenterLines);
2133     if (updateView) {
2134         KateViewInternal::updateView();
2135     }
2136 }
2137 
2138 void KateViewInternal::pageUp(bool sel, bool half)
2139 {
2140     if (view()->isCompletionActive()) {
2141         view()->completionWidget()->pageUp();
2142         return;
2143     }
2144     view()->clearSecondaryCursors();
2145 
2146     // jump back to where the cursor is, otherwise it is super
2147     // slow to call cache()->displayViewLine
2148     if (!view()->visibleRange().contains(m_displayCursor)) {
2149         scrollLines(m_displayCursor.line());
2150     }
2151 
2152     // remember the view line and x pos
2153     int viewLine = cache()->displayViewLine(m_displayCursor);
2154     bool atTop = startPos().atStartOfDocument();
2155 
2156     // Adjust for an auto-centering cursor
2157     int lineadj = m_minLinesVisible;
2158 
2159     int linesToScroll;
2160     if (!half) {
2161         linesToScroll = -qMax((linesDisplayed() - 1) - lineadj, 0);
2162     } else {
2163         linesToScroll = -qMax((linesDisplayed() / 2 - 1) - lineadj, 0);
2164     }
2165 
2166     m_preserveX = true;
2167 
2168     if (!doc()->pageUpDownMovesCursor() && !atTop) {
2169         KTextEditor::Cursor newStartPos = viewLineOffset(startPos(), linesToScroll - 1);
2170         scrollPos(newStartPos);
2171 
2172         // put the cursor back approximately where it was
2173         KTextEditor::Cursor newPos = toRealCursor(viewLineOffset(newStartPos, viewLine, true));
2174 
2175         KateTextLayout newLine = cache()->textLayout(newPos);
2176 
2177         newPos = renderer()->xToCursor(newLine, m_preservedX, !view()->wrapCursor());
2178 
2179         m_preserveX = true;
2180         updateSelection(newPos, sel);
2181         updateCursor(newPos);
2182 
2183     } else {
2184         scrollLines(linesToScroll, sel);
2185     }
2186 }
2187 
2188 void KateViewInternal::pageDown(bool sel, bool half)
2189 {
2190     if (view()->isCompletionActive()) {
2191         view()->completionWidget()->pageDown();
2192         return;
2193     }
2194 
2195     view()->clearSecondaryCursors();
2196 
2197     // jump back to where the cursor is, otherwise it is super
2198     // slow to call cache()->displayViewLine
2199     if (!view()->visibleRange().contains(m_displayCursor)) {
2200         scrollLines(m_displayCursor.line());
2201     }
2202 
2203     // remember the view line
2204     int viewLine = cache()->displayViewLine(m_displayCursor);
2205     bool atEnd = startPos() >= m_cachedMaxStartPos;
2206 
2207     // Adjust for an auto-centering cursor
2208     int lineadj = m_minLinesVisible;
2209 
2210     int linesToScroll;
2211     if (!half) {
2212         linesToScroll = qMax((linesDisplayed() - 1) - lineadj, 0);
2213     } else {
2214         linesToScroll = qMax((linesDisplayed() / 2 - 1) - lineadj, 0);
2215     }
2216 
2217     m_preserveX = true;
2218 
2219     if (!doc()->pageUpDownMovesCursor() && !atEnd) {
2220         KTextEditor::Cursor newStartPos = viewLineOffset(startPos(), linesToScroll + 1);
2221         scrollPos(newStartPos);
2222 
2223         // put the cursor back approximately where it was
2224         KTextEditor::Cursor newPos = toRealCursor(viewLineOffset(newStartPos, viewLine, true));
2225 
2226         KateTextLayout newLine = cache()->textLayout(newPos);
2227 
2228         newPos = renderer()->xToCursor(newLine, m_preservedX, !view()->wrapCursor());
2229 
2230         m_preserveX = true;
2231         updateSelection(newPos, sel);
2232         updateCursor(newPos);
2233 
2234     } else {
2235         scrollLines(linesToScroll, sel);
2236     }
2237 }
2238 
2239 int KateViewInternal::maxLen(int startLine)
2240 {
2241     Q_ASSERT(!view()->dynWordWrap());
2242 
2243     int displayLines = (view()->height() / renderer()->lineHeight()) + 1;
2244 
2245     int maxLen = 0;
2246 
2247     for (int z = 0; z < displayLines; z++) {
2248         int virtualLine = startLine + z;
2249 
2250         if (virtualLine < 0 || virtualLine >= (int)view()->textFolding().visibleLines()) {
2251             break;
2252         }
2253 
2254         const KateLineLayout *line = cache()->line(view()->textFolding().visibleLineToLine(virtualLine));
2255         if (!line) {
2256             continue;
2257         }
2258 
2259         maxLen = qMax(maxLen, line->width());
2260     }
2261 
2262     return maxLen;
2263 }
2264 
2265 bool KateViewInternal::columnScrollingPossible()
2266 {
2267     return !view()->dynWordWrap() && m_columnScroll->isEnabled() && (m_columnScroll->maximum() > 0);
2268 }
2269 
2270 bool KateViewInternal::lineScrollingPossible()
2271 {
2272     return m_lineScroll->minimum() != m_lineScroll->maximum();
2273 }
2274 
2275 void KateViewInternal::top(bool sel)
2276 {
2277     KTextEditor::Cursor newCursor(0, 0);
2278 
2279     newCursor = renderer()->xToCursor(cache()->textLayout(newCursor), m_preservedX, !view()->wrapCursor());
2280 
2281     view()->clearSecondaryCursors();
2282     updateSelection(newCursor, sel);
2283     updateCursor(newCursor);
2284 }
2285 
2286 void KateViewInternal::bottom(bool sel)
2287 {
2288     KTextEditor::Cursor newCursor(doc()->lastLine(), 0);
2289 
2290     newCursor = renderer()->xToCursor(cache()->textLayout(newCursor), m_preservedX, !view()->wrapCursor());
2291 
2292     view()->clearSecondaryCursors();
2293     updateSelection(newCursor, sel);
2294     updateCursor(newCursor);
2295 }
2296 
2297 void KateViewInternal::top_home(bool sel)
2298 {
2299     if (view()->isCompletionActive()) {
2300         view()->completionWidget()->top();
2301         return;
2302     }
2303 
2304     view()->clearSecondaryCursors();
2305     KTextEditor::Cursor c(0, 0);
2306     updateSelection(c, sel);
2307     updateCursor(c);
2308 }
2309 
2310 void KateViewInternal::bottom_end(bool sel)
2311 {
2312     if (view()->isCompletionActive()) {
2313         view()->completionWidget()->bottom();
2314         return;
2315     }
2316 
2317     view()->clearSecondaryCursors();
2318     KTextEditor::Cursor c(doc()->lastLine(), doc()->lineLength(doc()->lastLine()));
2319     updateSelection(c, sel);
2320     updateCursor(c);
2321 }
2322 
2323 void KateViewInternal::updateSecondarySelection(int cursorIdx, KTextEditor::Cursor old, KTextEditor::Cursor newPos) const
2324 {
2325     if (m_selectionMode != SelectionMode::Default) {
2326         view()->clearSecondaryCursors();
2327     }
2328 
2329     auto &secondaryCursors = view()->m_secondaryCursors;
2330     if (secondaryCursors.empty()) {
2331         qWarning() << "Invalid updateSecondarySelection with no secondaryCursors";
2332         return;
2333     }
2334     Q_ASSERT(secondaryCursors.size() > (size_t)cursorIdx);
2335 
2336     auto &cursor = secondaryCursors[cursorIdx];
2337     if (cursor.cursor() != newPos) {
2338         qWarning() << "Unexpected different cursor at cursorIdx" << cursorIdx << "found" << cursor.cursor() << "looking for: " << newPos;
2339         return;
2340     }
2341 
2342     if (cursor.range) {
2343         Q_ASSERT(cursor.anchor.isValid());
2344         cursor.range->setRange(cursor.anchor, newPos);
2345     } else {
2346         cursor.range.reset(view()->newSecondarySelectionRange({old, newPos}));
2347         cursor.anchor = old;
2348     }
2349 }
2350 
2351 void KateViewInternal::updateSelection(const KTextEditor::Cursor _newCursor, bool keepSel)
2352 {
2353     KTextEditor::Cursor newCursor = _newCursor;
2354     if (keepSel) {
2355         if (!view()->selection()
2356             || (m_selectAnchor.line() == -1)
2357             // don't kill the selection if we have a persistent selection and
2358             // the cursor is inside or at the boundaries of the selected area
2359             || (view()->config()->persistentSelection()
2360                 && !(view()->selectionRange().contains(m_cursor) || view()->selectionRange().boundaryAtCursor(m_cursor)))) {
2361             m_selectAnchor = m_cursor;
2362             setSelection(KTextEditor::Range(m_cursor, newCursor));
2363         } else {
2364             bool doSelect = true;
2365             switch (m_selectionMode) {
2366             case Word: {
2367                 // Restore selStartCached if needed. It gets nuked by
2368                 // viewSelectionChanged if we drag the selection into non-existence,
2369                 // which can legitimately happen if a shift+DC selection is unable to
2370                 // set a "proper" (i.e. non-empty) cached selection, e.g. because the
2371                 // start was on something that isn't a word. Word select mode relies
2372                 // on the cached selection being set properly, even if it is empty
2373                 // (i.e. selStartCached == selEndCached).
2374                 if (!m_selectionCached.isValid()) {
2375                     m_selectionCached.setStart(m_selectionCached.end());
2376                 }
2377 
2378                 int c;
2379                 if (newCursor > m_selectionCached.start()) {
2380                     m_selectAnchor = m_selectionCached.start();
2381 
2382                     Kate::TextLine l = doc()->kateTextLine(newCursor.line());
2383 
2384                     c = newCursor.column();
2385                     if (c > 0 && doc()->highlight()->isInWord(l.at(c - 1))) {
2386                         for (; c < l.length(); c++) {
2387                             if (!doc()->highlight()->isInWord(l.at(c))) {
2388                                 break;
2389                             }
2390                         }
2391                     }
2392 
2393                     newCursor.setColumn(c);
2394                 } else if (newCursor < m_selectionCached.start()) {
2395                     m_selectAnchor = m_selectionCached.end();
2396 
2397                     Kate::TextLine l = doc()->kateTextLine(newCursor.line());
2398 
2399                     c = newCursor.column();
2400                     if (c > 0 && c < doc()->lineLength(newCursor.line()) && doc()->highlight()->isInWord(l.at(c))
2401                         && doc()->highlight()->isInWord(l.at(c - 1))) {
2402                         for (c -= 2; c >= 0; c--) {
2403                             if (!doc()->highlight()->isInWord(l.at(c))) {
2404                                 break;
2405                             }
2406                         }
2407                         newCursor.setColumn(c + 1);
2408                     }
2409                 } else {
2410                     doSelect = false;
2411                 }
2412 
2413             } break;
2414             case Line:
2415                 if (!m_selectionCached.isValid()) {
2416                     m_selectionCached = KTextEditor::Range(endLine(), 0, endLine(), 0);
2417                 }
2418                 if (newCursor.line() > m_selectionCached.start().line()) {
2419                     if (newCursor.line() + 1 >= doc()->lines()) {
2420                         newCursor.setColumn(doc()->line(newCursor.line()).length());
2421                     } else {
2422                         newCursor.setPosition(newCursor.line() + 1, 0);
2423                     }
2424                     // Grow to include the entire line
2425                     m_selectAnchor = m_selectionCached.start();
2426                     m_selectAnchor.setColumn(0);
2427                 } else if (newCursor.line() < m_selectionCached.start().line()) {
2428                     newCursor.setColumn(0);
2429                     // Grow to include entire line
2430                     m_selectAnchor = m_selectionCached.end();
2431                     if (m_selectAnchor.column() > 0) {
2432                         if (m_selectAnchor.line() + 1 >= doc()->lines()) {
2433                             m_selectAnchor.setColumn(doc()->line(newCursor.line()).length());
2434                         } else {
2435                             m_selectAnchor.setPosition(m_selectAnchor.line() + 1, 0);
2436                         }
2437                     }
2438                 } else { // same line, ignore
2439                     doSelect = false;
2440                 }
2441                 break;
2442             case Mouse: {
2443                 if (!m_selectionCached.isValid()) {
2444                     break;
2445                 }
2446 
2447                 if (newCursor > m_selectionCached.end()) {
2448                     m_selectAnchor = m_selectionCached.start();
2449                 } else if (newCursor < m_selectionCached.start()) {
2450                     m_selectAnchor = m_selectionCached.end();
2451                 } else {
2452                     doSelect = false;
2453                 }
2454             } break;
2455             default: /* nothing special to do */;
2456             }
2457 
2458             if (doSelect) {
2459                 setSelection(KTextEditor::Range(m_selectAnchor, newCursor));
2460             } else if (m_selectionCached.isValid()) { // we have a cached selection, so we restore that
2461                 setSelection(m_selectionCached);
2462             }
2463         }
2464 
2465         m_selChangedByUser = true;
2466     } else if (!view()->config()->persistentSelection()) {
2467         view()->clearSelection();
2468 
2469         m_selectionCached = KTextEditor::Range::invalid();
2470         m_selectAnchor = KTextEditor::Cursor::invalid();
2471     }
2472 
2473 #ifndef QT_NO_ACCESSIBILITY
2474 //    FIXME KF5
2475 //    QAccessibleTextSelectionEvent ev(this, /* selection start, selection end*/);
2476 //    QAccessible::updateAccessibility(&ev);
2477 #endif
2478 }
2479 
2480 void KateViewInternal::setSelection(KTextEditor::Range range)
2481 {
2482     disconnect(m_view, &KTextEditor::ViewPrivate::selectionChanged, this, &KateViewInternal::viewSelectionChanged);
2483     view()->setSelection(range);
2484     connect(m_view, &KTextEditor::ViewPrivate::selectionChanged, this, &KateViewInternal::viewSelectionChanged);
2485 }
2486 
2487 void KateViewInternal::moveCursorToSelectionEdge(bool scroll)
2488 {
2489     if (!view()->selection()) {
2490         return;
2491     }
2492 
2493     int tmp = m_minLinesVisible;
2494     m_minLinesVisible = 0;
2495 
2496     if (view()->selectionRange().start() < m_selectAnchor) {
2497         updateCursor(view()->selectionRange().start(), false, false, false, scroll);
2498     } else {
2499         updateCursor(view()->selectionRange().end(), false, false, false, scroll);
2500     }
2501     if (!scroll) {
2502         m_madeVisible = false;
2503     }
2504 
2505     m_minLinesVisible = tmp;
2506 }
2507 
2508 KTextEditor::Range KateViewInternal::findMatchingFoldingMarker(const KTextEditor::Cursor currentCursorPos,
2509                                                                const KSyntaxHighlighting::FoldingRegion foldingRegion,
2510                                                                const int maxLines)
2511 {
2512     const int direction = (foldingRegion.type() == KSyntaxHighlighting::FoldingRegion::Begin) ? 1 : -1;
2513     int foldCounter = 0;
2514     int lineCounter = 0;
2515     const auto foldMarkers = m_view->doc()->buffer().computeFoldings(currentCursorPos.line());
2516 
2517     // searching a end folding marker? go left to right
2518     // otherwise, go right to left
2519     long i = direction == 1 ? 0 : (long)foldMarkers.size() - 1;
2520 
2521     // For the first line, we start considering the first folding after the cursor
2522     for (; i >= 0 && i < (long)foldMarkers.size(); i += direction) {
2523         if ((foldMarkers[i].offset - currentCursorPos.column()) * direction > 0 && foldMarkers[i].foldingRegion.id() == foldingRegion.id()) {
2524             if (foldMarkers[i].foldingRegion.type() == foldingRegion.type()) {
2525                 foldCounter += 1;
2526             } else if (foldCounter > 0) {
2527                 foldCounter -= 1;
2528             } else if (foldCounter == 0) {
2529                 return KTextEditor::Range(currentCursorPos.line(),
2530                                           getStartOffset(direction, foldMarkers[i].offset, foldMarkers[i].length),
2531                                           currentCursorPos.line(),
2532                                           getEndOffset(direction, foldMarkers[i].offset, foldMarkers[i].length));
2533             }
2534         }
2535     }
2536 
2537     // for the other lines
2538     int currentLine = currentCursorPos.line() + direction;
2539     for (; currentLine >= 0 && currentLine < m_view->doc()->lines() && lineCounter < maxLines; currentLine += direction) {
2540         // update line attributes
2541         const auto foldMarkers = m_view->doc()->buffer().computeFoldings(currentLine);
2542         i = direction == 1 ? 0 : (long)foldMarkers.size() - 1;
2543 
2544         // iterate through the markers
2545         for (; i >= 0 && i < (long)foldMarkers.size(); i += direction) {
2546             if (foldMarkers[i].foldingRegion.id() == foldingRegion.id()) {
2547                 if (foldMarkers[i].foldingRegion.type() == foldingRegion.type()) {
2548                     foldCounter += 1;
2549                 } else if (foldCounter != 0) {
2550                     foldCounter -= 1;
2551                 } else if (foldCounter == 0) {
2552                     return KTextEditor::Range(currentLine,
2553                                               getStartOffset(direction, foldMarkers[i].offset, foldMarkers[i].length),
2554                                               currentLine,
2555                                               getEndOffset(direction, foldMarkers[i].offset, foldMarkers[i].length));
2556                 }
2557             }
2558         }
2559         lineCounter += 1;
2560     }
2561 
2562     // got out of loop, no matching folding found
2563     // returns a invalid folding range
2564     return KTextEditor::Range::invalid();
2565 }
2566 
2567 void KateViewInternal::updateFoldingMarkersHighlighting()
2568 {
2569     const auto foldings = m_view->doc()->buffer().computeFoldings(m_cursor.line());
2570     for (unsigned long i = 0; i < foldings.size(); i++) {
2571         // 1 -> left to right, the current folding is start type
2572         // -1 -> right to left, the current folding is end type
2573         int direction = (foldings[i].foldingRegion.type() == KSyntaxHighlighting::FoldingRegion::Begin) ? 1 : -1;
2574 
2575         int startOffset = getStartOffset(-direction, foldings[i].offset, foldings[i].length);
2576         int endOffset = getEndOffset(-direction, foldings[i].offset, foldings[i].length);
2577 
2578         if (m_cursor.column() >= startOffset && m_cursor.column() <= endOffset) {
2579             const auto foldingMarkerMatch = findMatchingFoldingMarker(KTextEditor::Cursor(m_cursor.line(), m_cursor.column()), foldings[i].foldingRegion, 2000);
2580 
2581             if (!foldingMarkerMatch.isValid()) {
2582                 break;
2583             }
2584 
2585             // set fmStart to Opening Folding Marker and fmEnd to Ending Folding Marker
2586             if (direction == 1) {
2587                 m_fmStart->setRange(KTextEditor::Range(m_cursor.line(), startOffset, m_cursor.line(), endOffset));
2588                 m_fmEnd->setRange(foldingMarkerMatch);
2589             } else {
2590                 m_fmStart->setRange(foldingMarkerMatch);
2591                 m_fmEnd->setRange(KTextEditor::Range(m_cursor.line(), startOffset, m_cursor.line(), endOffset));
2592             }
2593 
2594             KTextEditor::Attribute::Ptr fill = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute());
2595             fill->setBackground(view()->rendererConfig()->highlightedBracketColor());
2596 
2597             m_fmStart->setAttribute(fill);
2598             m_fmEnd->setAttribute(fill);
2599             return;
2600         }
2601     }
2602     m_fmStart->setRange(KTextEditor::Range::invalid());
2603     m_fmEnd->setRange(KTextEditor::Range::invalid());
2604 }
2605 
2606 void KateViewInternal::updateSecondaryCursors(const QVarLengthArray<CursorPair, 16> &cursors, bool sel)
2607 {
2608     if (sel) {
2609         for (int i = 0; i < cursors.size(); ++i) {
2610             updateSecondarySelection(i, cursors[i].oldPos, cursors[i].newPos);
2611         }
2612         if (!cursors.isEmpty()) {
2613             mergeSelections();
2614         }
2615     } else {
2616         view()->clearSecondarySelections();
2617     }
2618 
2619     QVarLengthArray<int> linesToUpdate;
2620     for (auto cpair : cursors) {
2621         linesToUpdate.push_back(cpair.oldPos.line());
2622         linesToUpdate.push_back(cpair.newPos.line());
2623     }
2624     // Remove duplicate stuff
2625     std::sort(linesToUpdate.begin(), linesToUpdate.end());
2626     auto it = std::unique(linesToUpdate.begin(), linesToUpdate.end());
2627 
2628     // Collapse ranges to avoid extra work
2629     using Range = std::pair<int, int>; // start, length
2630     QVarLengthArray<Range> ranges;
2631     int prev = 0;
2632     for (auto i = linesToUpdate.begin(); i != it; ++i) {
2633         int curLine = *i;
2634         if (!ranges.isEmpty() && prev + 1 == curLine) {
2635             ranges.back().second++;
2636         } else {
2637             ranges.push_back({curLine, 0});
2638         }
2639         prev = curLine;
2640     }
2641 
2642     for (auto range : ranges) {
2643         int startLine = range.first;
2644         int endLine = range.first + range.second;
2645         tagLines(startLine, endLine, /*realLines=*/true);
2646     }
2647     updateDirty();
2648 }
2649 
2650 void KateViewInternal::mergeSelections()
2651 {
2652     using SecondaryCursor = KTextEditor::ViewPrivate::SecondaryCursor;
2653     using Range = KTextEditor::Range;
2654     auto doMerge = [](Range newRange, SecondaryCursor &a, SecondaryCursor &b) {
2655         a.range->setRange(newRange);
2656 
2657         b.pos.reset();
2658         b.range.reset();
2659     };
2660 
2661     auto &cursors = view()->m_secondaryCursors;
2662     for (auto it = cursors.begin(); it != cursors.end(); ++it) {
2663         if (!it->range)
2664             continue;
2665         if (it + 1 == cursors.end()) {
2666             break;
2667         }
2668 
2669         auto n = std::next(it);
2670         if (/*!it->range || */ !n->range) {
2671             continue;
2672         }
2673 
2674         auto curRange = it->range->toRange();
2675         auto nextRange = n->range->toRange();
2676         if (!curRange.overlaps(nextRange)) {
2677             continue;
2678         }
2679 
2680         bool isLefSel = it->cursor() < it->anchor;
2681 
2682         // if the ranges overlap we expand the next range
2683         // to include the current one. This allows us to
2684         // check all ranges for overlap in one go
2685         auto curPos = it->cursor();
2686         nextRange.expandToRange(curRange);
2687         if (isLefSel) {
2688             // in left selection our next cursor
2689             // is ahead somewhere, we want to keep
2690             // the smallest position
2691             n->pos->setPosition(curPos);
2692             n->anchor = qMax(n->anchor, it->anchor);
2693         } else {
2694             n->anchor = qMin(n->anchor, it->anchor);
2695         }
2696         doMerge(nextRange, *n, *it);
2697     }
2698 
2699     if (view()->selection()) {
2700         auto primarySel = view()->m_selection.toRange();
2701         auto primCursor = cursorPosition();
2702         for (auto it = cursors.begin(); it != cursors.end(); ++it) {
2703             // If range is valid, we merge selection into primary
2704             // Otherwise if cursor is inside primary selection, it
2705             // is removed
2706             auto curRange = it->range ? it->range->toRange() : Range::invalid();
2707             if (curRange.isValid() && primarySel.overlaps(curRange)) {
2708                 primarySel.expandToRange(curRange);
2709                 bool isLeft = it->cursor() < it->anchor;
2710 
2711                 if (isLeft) {
2712                     if (it->cursor() < primCursor) {
2713                         updateCursor(it->cursor());
2714                     }
2715                     m_selectAnchor = qMax(m_selectAnchor, it->anchor);
2716                 } else {
2717                     if (it->cursor() > primCursor) {
2718                         updateCursor(it->cursor());
2719                     }
2720                     m_selectAnchor = qMin(m_selectAnchor, it->anchor);
2721                 }
2722 
2723                 setSelection(primarySel);
2724                 it->pos.reset();
2725                 it->range.reset();
2726             } else if (it->pos) {
2727                 // This only needs to be done for primary selection
2728                 // because remember, mouse selection is always with
2729                 // primary cursor
2730                 auto pos = it->cursor();
2731                 if (!primarySel.boundaryAtCursor(pos) && primarySel.contains(pos)) {
2732                     it->pos.reset();
2733                 }
2734             }
2735         }
2736     }
2737 
2738     cursors.erase(std::remove_if(cursors.begin(),
2739                                  cursors.end(),
2740                                  [](const SecondaryCursor &c) {
2741                                      return !c.pos.get();
2742                                  }),
2743                   cursors.end());
2744 }
2745 
2746 void KateViewInternal::updateCursor(const KTextEditor::Cursor newCursor, bool force, bool center, bool calledExternally, bool scroll)
2747 {
2748     if (!force && (m_cursor.toCursor() == newCursor)) {
2749         m_displayCursor = toVirtualCursor(newCursor);
2750         if (scroll && !m_madeVisible && m_view == doc()->activeView()) {
2751             // unfold if required
2752             view()->textFolding().ensureLineIsVisible(newCursor.line());
2753 
2754             makeVisible(m_displayCursor, m_displayCursor.column(), false, center, calledExternally);
2755         }
2756 
2757         return;
2758     }
2759 
2760     if (m_cursor.line() != newCursor.line()) {
2761         m_leftBorder->updateForCursorLineChange();
2762     }
2763 
2764     // unfold if required
2765     view()->textFolding().ensureLineIsVisible(newCursor.line());
2766 
2767     KTextEditor::Cursor oldDisplayCursor = m_displayCursor;
2768 
2769     m_displayCursor = toVirtualCursor(newCursor);
2770     m_cursor.setPosition(newCursor);
2771 
2772     if (m_view == doc()->activeView() && scroll) {
2773         makeVisible(m_displayCursor, m_displayCursor.column(), false, center, calledExternally);
2774     }
2775 
2776     updateBracketMarks();
2777 
2778     updateFoldingMarkersHighlighting();
2779 
2780     // avoid double work, tagLine => tagLines => not that cheap, much more costly than to compare 2 ints
2781     tagLine(oldDisplayCursor);
2782     if (oldDisplayCursor.line() != m_displayCursor.line()) {
2783         tagLine(m_displayCursor);
2784     }
2785 
2786     updateMicroFocus();
2787 
2788     if (m_cursorTimer.isActive()) {
2789         if (QApplication::cursorFlashTime() > 0) {
2790             m_cursorTimer.start(QApplication::cursorFlashTime() / 2);
2791         }
2792         renderer()->setDrawCaret(true);
2793     }
2794 
2795     // Remember the maximum X position if requested
2796     if (m_preserveX) {
2797         m_preserveX = false;
2798     } else {
2799         m_preservedX = renderer()->cursorToX(cache()->textLayout(m_cursor), m_cursor, !view()->wrapCursor());
2800     }
2801 
2802     // qCDebug(LOG_KTE) << "m_preservedX: " << m_preservedX << " (was "<< oldmaxx << "), m_cursorX: " << m_cursorX;
2803     // qCDebug(LOG_KTE) << "Cursor now located at real " << cursor.line << "," << cursor.col << ", virtual " << m_displayCursor.line << ", " <<
2804     // m_displayCursor.col << "; Top is " << startLine() << ", " << startPos().col;
2805 
2806     cursorMoved();
2807 
2808     updateDirty(); // paintText(0, 0, width(), height(), true);
2809 
2810     Q_EMIT view()->cursorPositionChanged(m_view, m_cursor);
2811 }
2812 
2813 void KateViewInternal::updateBracketMarkAttributes()
2814 {
2815     KTextEditor::Attribute::Ptr bracketFill = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute());
2816     bracketFill->setBackground(view()->rendererConfig()->highlightedBracketColor());
2817     bracketFill->setBackgroundFillWhitespace(false);
2818     if (QFontInfo(renderer()->currentFont()).fixedPitch()) {
2819         // make font bold only for fixed fonts, otherwise text jumps around
2820         bracketFill->setFontBold();
2821     }
2822 
2823     m_bmStart->setAttribute(bracketFill);
2824     m_bmEnd->setAttribute(bracketFill);
2825 
2826     if (view()->rendererConfig()->showWholeBracketExpression()) {
2827         KTextEditor::Attribute::Ptr expressionFill = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute());
2828         expressionFill->setBackground(view()->rendererConfig()->highlightedBracketColor());
2829         expressionFill->setBackgroundFillWhitespace(false);
2830 
2831         m_bm->setAttribute(expressionFill);
2832     } else {
2833         m_bm->setAttribute(KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()));
2834     }
2835 }
2836 
2837 void KateViewInternal::updateBracketMarks()
2838 {
2839     // add some limit to this, this is really endless on big files without limit
2840     const int maxLines = 5000;
2841     const KTextEditor::Range newRange = doc()->findMatchingBracket(m_cursor, maxLines);
2842 
2843     // new range valid, then set ranges to it
2844     if (newRange.isValid()) {
2845         if (m_bm->toRange() == newRange) {
2846             // hide preview as it now (probably) blocks the top of the view
2847             hideBracketMatchPreview();
2848             return;
2849         }
2850 
2851         // modify full range
2852         m_bm->setRange(newRange);
2853 
2854         // modify start and end ranges
2855         m_bmStart->setRange(KTextEditor::Range(m_bm->start(), KTextEditor::Cursor(m_bm->start().line(), m_bm->start().column() + 1)));
2856         m_bmEnd->setRange(KTextEditor::Range(m_bm->end(), KTextEditor::Cursor(m_bm->end().line(), m_bm->end().column() + 1)));
2857 
2858         // show preview of the matching bracket's line
2859         if (m_view->config()->value(KateViewConfig::ShowBracketMatchPreview).toBool()) {
2860             showBracketMatchPreview();
2861         }
2862 
2863         // flash matching bracket
2864         if (!m_view->rendererConfig()->animateBracketMatching()) {
2865             return;
2866         }
2867 
2868         const KTextEditor::Cursor flashPos = (m_cursor == m_bmStart->start() || m_cursor == m_bmStart->end()) ? m_bmEnd->start() : m_bm->start();
2869         if (flashPos != m_bmLastFlashPos->toCursor()) {
2870             m_bmLastFlashPos->setPosition(flashPos);
2871 
2872             KTextEditor::Attribute::Ptr attribute = attributeAt(flashPos);
2873             attribute->setBackground(view()->rendererConfig()->highlightedBracketColor());
2874             if (m_bmStart->attribute()->fontBold()) {
2875                 attribute->setFontBold(true);
2876             }
2877 
2878             flashChar(flashPos, attribute);
2879         }
2880         return;
2881     }
2882 
2883     // new range was invalid
2884     m_bm->setRange(KTextEditor::Range::invalid());
2885     m_bmStart->setRange(KTextEditor::Range::invalid());
2886     m_bmEnd->setRange(KTextEditor::Range::invalid());
2887     m_bmLastFlashPos->setPosition(KTextEditor::Cursor::invalid());
2888     hideBracketMatchPreview();
2889 }
2890 
2891 bool KateViewInternal::tagLine(const KTextEditor::Cursor virtualCursor)
2892 {
2893     // we had here some special case handling for one line, it was just randomly wrong for dyn. word wrapped stuff => use the generic function
2894     return tagLines(virtualCursor, virtualCursor, false);
2895 }
2896 
2897 bool KateViewInternal::tagLines(int start, int end, bool realLines)
2898 {
2899     return tagLines(KTextEditor::Cursor(start, 0), KTextEditor::Cursor(end, -1), realLines);
2900 }
2901 
2902 bool KateViewInternal::tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end, bool realCursors)
2903 {
2904     if (realCursors) {
2905         cache()->relayoutLines(start.line(), end.line());
2906 
2907         // qCDebug(LOG_KTE)<<"realLines is true";
2908         start = toVirtualCursor(start);
2909         end = toVirtualCursor(end);
2910 
2911     } else {
2912         cache()->relayoutLines(toRealCursor(start).line(), toRealCursor(end).line());
2913     }
2914 
2915     if (end.line() < startLine()) {
2916         // qCDebug(LOG_KTE)<<"end<startLine";
2917         return false;
2918     }
2919     // Used to be > endLine(), but cache may not be valid when checking, so use a
2920     // less optimal but still adequate approximation (potential overestimation but minimal performance difference)
2921     if (start.line() > startLine() + cache()->viewCacheLineCount()) {
2922         // qCDebug(LOG_KTE)<<"start> endLine"<<start<<" "<<(endLine());
2923         return false;
2924     }
2925 
2926     cache()->updateViewCache(startPos());
2927 
2928     // qCDebug(LOG_KTE) << "tagLines( [" << start << "], [" << end << "] )";
2929 
2930     bool ret = false;
2931 
2932     for (int z = 0; z < cache()->viewCacheLineCount(); z++) {
2933         KateTextLayout &line = cache()->viewLine(z);
2934         if ((line.virtualLine() > start.line() || (line.virtualLine() == start.line() && line.endCol() >= start.column() && start.column() != -1))
2935             && (line.virtualLine() < end.line() || (line.virtualLine() == end.line() && (line.startCol() <= end.column() || end.column() == -1)))) {
2936             ret = true;
2937             break;
2938             // qCDebug(LOG_KTE) << "Tagged line " << line.line();
2939         }
2940     }
2941 
2942     // full update of border it we have indentation based hl and show the markers always
2943     if (!m_view->config()->showFoldingOnHoverOnly() && doc()->highlight() && doc()->highlight()->foldingIndentationSensitive()) {
2944         // not the default setting, can be optimized if ever some issue
2945         m_leftBorder->update();
2946     } else if (!view()->dynWordWrap()) {
2947         int y = lineToY(start.line());
2948         // FIXME is this enough for when multiple lines are deleted
2949         int h = (end.line() - start.line() + 2) * renderer()->lineHeight();
2950         if (end.line() >= view()->textFolding().visibleLines() - 1) {
2951             h = height();
2952         }
2953 
2954         m_leftBorder->update(0, y, m_leftBorder->width(), h);
2955     } else {
2956         // FIXME Do we get enough good info in editRemoveText to optimize this more?
2957         // bool justTagged = false;
2958         for (int z = 0; z < cache()->viewCacheLineCount(); z++) {
2959             KateTextLayout &line = cache()->viewLine(z);
2960             if (!line.isValid()
2961                 || ((line.virtualLine() > start.line() || (line.virtualLine() == start.line() && line.endCol() >= start.column() && start.column() != -1))
2962                     && (line.virtualLine() < end.line() || (line.virtualLine() == end.line() && (line.startCol() <= end.column() || end.column() == -1))))) {
2963                 // justTagged = true;
2964                 m_leftBorder->update(0, z * renderer()->lineHeight(), m_leftBorder->width(), m_leftBorder->height());
2965                 break;
2966             }
2967             /*else if (justTagged)
2968             {
2969               justTagged = false;
2970               leftBorder->update (0, z * doc()->viewFont.fontHeight, leftBorder->width(), doc()->viewFont.fontHeight);
2971               break;
2972             }*/
2973         }
2974     }
2975 
2976     return ret;
2977 }
2978 
2979 bool KateViewInternal::tagRange(KTextEditor::Range range, bool realCursors)
2980 {
2981     return tagLines(range.start(), range.end(), realCursors);
2982 }
2983 
2984 void KateViewInternal::tagAll()
2985 {
2986     // clear the cache...
2987     cache()->clear();
2988 
2989     m_leftBorder->updateFont();
2990     m_leftBorder->update();
2991 }
2992 
2993 void KateViewInternal::paintCursor()
2994 {
2995     if (tagLine(m_displayCursor)) {
2996         updateDirty(); // paintText (0,0,width(), height(), true);
2997     }
2998 
2999     const int s = view()->firstDisplayedLine();
3000     const int e = view()->lastDisplayedLine();
3001     for (const auto &c : view()->m_secondaryCursors) {
3002         auto p = c.cursor();
3003         if (p.line() >= s - 1 && p.line() <= e + 1) {
3004             tagLines(p, p, true);
3005         }
3006     }
3007 
3008     updateDirty(); // paintText (0,0,width(), height(), true);
3009 }
3010 
3011 KTextEditor::Cursor KateViewInternal::cursorForPoint(QPoint p)
3012 {
3013     KateTextLayout thisLine = yToKateTextLayout(p.y());
3014     KTextEditor::Cursor c;
3015 
3016     if (!thisLine.isValid()) { // probably user clicked below the last line -> use the last line
3017         thisLine = cache()->textLayout(doc()->lines() - 1, -1);
3018     }
3019 
3020     c = renderer()->xToCursor(thisLine, startX() + p.x(), !view()->wrapCursor());
3021 
3022     if (c.line() < 0 || c.line() >= doc()->lines()) {
3023         return KTextEditor::Cursor::invalid();
3024     }
3025 
3026     // loop over all notes and check if the point is inside it
3027     const auto inlineNotes = view()->inlineNotes(c.line());
3028     p = mapToGlobal(p);
3029     for (const auto &note : inlineNotes) {
3030         auto noteCursor = note.m_position;
3031         // we are not interested in notes that are past the end or at 0
3032         if (note.m_position.column() >= doc()->lineLength(c.line()) || note.m_position.column() == 0) {
3033             continue;
3034         }
3035         // an inline note is "part" of a char i.e., char width is increased so that
3036         // an inline note can be painted beside it. So we need to find the actual
3037         // char width
3038         const auto caretWidth = renderer()->caretStyle() == KTextEditor::caretStyles::Line ? 2. : 0.;
3039         const auto width = KTextEditor::InlineNote(note).width() + caretWidth;
3040         const auto charWidth = renderer()->currentFontMetrics().horizontalAdvance(doc()->characterAt(noteCursor));
3041         const auto halfCharWidth = (charWidth / 2);
3042         // we leave the first half of the char width. If the user has clicked in the first half, let it go the
3043         // previous column.
3044         const auto totalWidth = width + halfCharWidth;
3045         auto start = mapToGlobal(cursorToCoordinate(noteCursor, true, false));
3046         start = start - QPoint(totalWidth, 0);
3047         QRect r(start, QSize{(int)halfCharWidth, renderer()->lineHeight()});
3048         if (r.contains(p)) {
3049             c = noteCursor;
3050             break;
3051         }
3052     }
3053 
3054     return c;
3055 }
3056 
3057 // Point in content coordinates
3058 void KateViewInternal::placeCursor(const QPoint &p, bool keepSelection, bool updateSelection)
3059 {
3060     KTextEditor::Cursor c = cursorForPoint(p);
3061     if (!c.isValid()) {
3062         return;
3063     }
3064 
3065     if (updateSelection) {
3066         KateViewInternal::updateSelection(c, keepSelection);
3067     }
3068 
3069     int tmp = m_minLinesVisible;
3070     m_minLinesVisible = 0;
3071     updateCursor(c);
3072     m_minLinesVisible = tmp;
3073 
3074     if (updateSelection && keepSelection) {
3075         moveCursorToSelectionEdge();
3076     }
3077 }
3078 
3079 // Point in content coordinates
3080 bool KateViewInternal::isTargetSelected(const QPoint &p)
3081 {
3082     const KateTextLayout &thisLine = yToKateTextLayout(p.y());
3083     if (!thisLine.isValid()) {
3084         return false;
3085     }
3086 
3087     return view()->cursorSelected(renderer()->xToCursor(thisLine, startX() + p.x(), !view()->wrapCursor()));
3088 }
3089 
3090 // BEGIN EVENT HANDLING STUFF
3091 
3092 bool KateViewInternal::eventFilter(QObject *obj, QEvent *e)
3093 {
3094     switch (e->type()) {
3095     case QEvent::ChildAdded:
3096     case QEvent::ChildRemoved: {
3097         QChildEvent *c = static_cast<QChildEvent *>(e);
3098         if (c->added()) {
3099             c->child()->installEventFilter(this);
3100 
3101         } else if (c->removed()) {
3102             c->child()->removeEventFilter(this);
3103         }
3104     } break;
3105 
3106     case QEvent::ShortcutOverride: {
3107         QKeyEvent *k = static_cast<QKeyEvent *>(e);
3108 
3109         if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) {
3110             if (!view()->m_secondaryCursors.empty()) {
3111                 view()->clearSecondaryCursors();
3112                 k->accept();
3113                 return true;
3114             }
3115 
3116             if (view()->isCompletionActive()) {
3117                 view()->abortCompletion();
3118                 k->accept();
3119                 // qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "aborting completion";
3120                 return true;
3121             } else if (view()->bottomViewBar()->barWidgetVisible()) {
3122                 view()->bottomViewBar()->hideCurrentBarWidget();
3123                 k->accept();
3124                 // qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "closing view bar";
3125                 return true;
3126             } else if (!view()->config()->persistentSelection() && view()->selection()) {
3127                 m_currentInputMode->clearSelection();
3128                 k->accept();
3129                 // qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "clearing selection";
3130                 return true;
3131             }
3132         }
3133 
3134         if (m_currentInputMode->stealKey(k)) {
3135             k->accept();
3136             return true;
3137         }
3138 
3139         // CompletionReplayer.replay only gets called when a Ctrl-Space gets to InsertViMode::handleKeyPress
3140         // Workaround for BUG: 334032 (https://bugs.kde.org/show_bug.cgi?id=334032)
3141         if (k->key() == Qt::Key_Space && k->modifiers() == Qt::ControlModifier) {
3142             keyPressEvent(k);
3143             if (k->isAccepted()) {
3144                 return true;
3145             }
3146         }
3147     } break;
3148 
3149     case QEvent::KeyPress: {
3150         QKeyEvent *k = static_cast<QKeyEvent *>(e);
3151 
3152         // Override all other single key shortcuts which do not use a modifier other than Shift
3153         if (obj == this && (!k->modifiers() || k->modifiers() == Qt::ShiftModifier)) {
3154             keyPressEvent(k);
3155             if (k->isAccepted()) {
3156                 // qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "using keystroke";
3157                 return true;
3158             }
3159         }
3160 
3161         // qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "ignoring";
3162     } break;
3163 
3164     case QEvent::DragMove: {
3165         QPoint currentPoint = ((QDragMoveEvent *)e)->position().toPoint();
3166 
3167         QRect doNotScrollRegion(s_scrollMargin, s_scrollMargin, width() - s_scrollMargin * 2, height() - s_scrollMargin * 2);
3168 
3169         if (!doNotScrollRegion.contains(currentPoint)) {
3170             startDragScroll();
3171             // Keep sending move events
3172             ((QDragMoveEvent *)e)->accept(QRect(0, 0, 0, 0));
3173         }
3174 
3175         dragMoveEvent((QDragMoveEvent *)e);
3176     } break;
3177 
3178     case QEvent::DragLeave:
3179         // happens only when pressing ESC while dragging
3180         stopDragScroll();
3181         break;
3182 
3183     case QEvent::WindowDeactivate:
3184         hideBracketMatchPreview();
3185         break;
3186 
3187     case QEvent::ScrollPrepare: {
3188         QScrollPrepareEvent *s = static_cast<QScrollPrepareEvent *>(e);
3189         scrollPrepareEvent(s);
3190     }
3191         return true;
3192 
3193     case QEvent::Scroll: {
3194         QScrollEvent *s = static_cast<QScrollEvent *>(e);
3195         scrollEvent(s);
3196     }
3197         return true;
3198 
3199     default:
3200         break;
3201     }
3202 
3203     return QWidget::eventFilter(obj, e);
3204 }
3205 
3206 void KateViewInternal::keyPressEvent(QKeyEvent *e)
3207 {
3208     m_shiftKeyPressed = e->modifiers() & Qt::ShiftModifier;
3209     if (e->key() == Qt::Key_Left && e->modifiers() == Qt::AltModifier) {
3210         view()->emitNavigateLeft();
3211         e->setAccepted(true);
3212         return;
3213     }
3214     if (e->key() == Qt::Key_Right && e->modifiers() == Qt::AltModifier) {
3215         view()->emitNavigateRight();
3216         e->setAccepted(true);
3217         return;
3218     }
3219     if (e->key() == Qt::Key_Up && e->modifiers() == Qt::AltModifier) {
3220         view()->emitNavigateUp();
3221         e->setAccepted(true);
3222         return;
3223     }
3224     if (e->key() == Qt::Key_Down && e->modifiers() == Qt::AltModifier) {
3225         view()->emitNavigateDown();
3226         e->setAccepted(true);
3227         return;
3228     }
3229     if (e->key() == Qt::Key_Return && e->modifiers() == Qt::AltModifier) {
3230         view()->emitNavigateAccept();
3231         e->setAccepted(true);
3232         return;
3233     }
3234     if (e->key() == Qt::Key_Backspace && e->modifiers() == Qt::AltModifier) {
3235         view()->emitNavigateBack();
3236         e->setAccepted(true);
3237         return;
3238     }
3239 
3240     if (e->key() == Qt::Key_Alt && view()->completionWidget()->isCompletionActive()) {
3241         view()->completionWidget()->toggleDocumentation();
3242     }
3243 
3244     // Note: AND'ing with <Shift> is a quick hack to fix Key_Enter
3245     const int key = e->key() | (e->modifiers() & Qt::ShiftModifier);
3246 
3247     if (m_currentInputMode->keyPress(e)) {
3248         return;
3249     }
3250 
3251     if (!doc()->isReadWrite()) {
3252         e->ignore();
3253         return;
3254     }
3255 
3256     if ((key == Qt::Key_Return) || (key == Qt::Key_Enter) || key == (Qt::SHIFT | Qt::Key_Return).toCombined()
3257         || key == (Qt::SHIFT | Qt::Key_Enter).toCombined()) {
3258         view()->keyReturn();
3259         e->accept();
3260         return;
3261     }
3262 
3263     if (key == Qt::Key_Backspace || key == (Qt::SHIFT | Qt::Key_Backspace).toCombined()) {
3264         // view()->backspace();
3265         e->accept();
3266 
3267         return;
3268     }
3269 
3270     if (key == Qt::Key_Tab || key == (Qt::SHIFT | Qt::Key_Backtab).toCombined() || key == Qt::Key_Backtab) {
3271         if (key == Qt::Key_Tab) {
3272             uint tabHandling = doc()->config()->tabHandling();
3273             // convert tabSmart into tabInsertsTab or tabIndents:
3274             if (tabHandling == KateDocumentConfig::tabSmart) {
3275                 // multiple lines selected
3276                 if (view()->selection() && !view()->selectionRange().onSingleLine()) {
3277                     tabHandling = KateDocumentConfig::tabIndents;
3278                 }
3279 
3280                 // otherwise: take look at cursor position
3281                 else {
3282                     // if the cursor is at or before the first non-space character
3283                     // or on an empty line,
3284                     // Tab indents, otherwise it inserts a tab character.
3285                     Kate::TextLine line = doc()->kateTextLine(m_cursor.line());
3286                     int first = line.firstChar();
3287                     if (first < 0 || m_cursor.column() <= first) {
3288                         tabHandling = KateDocumentConfig::tabIndents;
3289                     } else {
3290                         tabHandling = KateDocumentConfig::tabInsertsTab;
3291                     }
3292                 }
3293             }
3294 
3295             // either we just insert a tab or we convert that into an indent action
3296             if (tabHandling == KateDocumentConfig::tabInsertsTab) {
3297                 doc()->typeChars(m_view, QStringLiteral("\t"));
3298             } else {
3299                 doc()->editStart();
3300                 for (const auto &c : std::as_const(m_view->m_secondaryCursors)) {
3301                     auto cursor = c.cursor();
3302                     doc()->indent(KTextEditor::Range(cursor.line(), 0, cursor.line(), 0), 1);
3303                 }
3304 
3305                 doc()->indent(view()->selection() ? view()->selectionRange() : KTextEditor::Range(m_cursor.line(), 0, m_cursor.line(), 0), 1);
3306                 doc()->editEnd();
3307             }
3308 
3309             e->accept();
3310 
3311             return;
3312         } else if (doc()->config()->tabHandling() != KateDocumentConfig::tabInsertsTab) {
3313             // key == Qt::SHIFT+Qt::Key_Backtab || key == Qt::Key_Backtab
3314             doc()->indent(view()->selection() ? view()->selectionRange() : KTextEditor::Range(m_cursor.line(), 0, m_cursor.line(), 0), -1);
3315             e->accept();
3316 
3317             return;
3318         }
3319     }
3320 
3321     if (isAcceptableInput(e)) {
3322         doc()->typeChars(m_view, e->text());
3323         e->accept();
3324         return;
3325     }
3326 
3327     e->ignore();
3328 }
3329 
3330 void KateViewInternal::keyReleaseEvent(QKeyEvent *e)
3331 {
3332     if (m_shiftKeyPressed && (e->modifiers() & Qt::ShiftModifier) == 0) {
3333         m_shiftKeyPressed = false;
3334 
3335         if (m_selChangedByUser) {
3336             if (view()->selection()) {
3337                 QApplication::clipboard()->setText(view()->selectionText(), QClipboard::Selection);
3338             }
3339 
3340             m_selChangedByUser = false;
3341         }
3342     }
3343 
3344     e->ignore();
3345     return;
3346 }
3347 
3348 bool KateViewInternal::isAcceptableInput(const QKeyEvent *e)
3349 {
3350     // reimplemented from QInputControl::isAcceptableInput()
3351 
3352     const QString text = e->text();
3353     if (text.isEmpty()) {
3354         return false;
3355     }
3356 
3357     const QChar c = text.at(0);
3358 
3359     // Formatting characters such as ZWNJ, ZWJ, RLM, etc. This needs to go before the
3360     // next test, since CTRL+SHIFT is sometimes used to input it on Windows.
3361     // see bug 389796 (typing formatting characters such as ZWNJ)
3362     // and bug 396764 (typing soft-hyphens)
3363     if (c.category() == QChar::Other_Format) {
3364         return true;
3365     }
3366 
3367     // QTBUG-35734: ignore Ctrl/Ctrl+Shift; accept only AltGr (Alt+Ctrl) on German keyboards
3368     if ((e->modifiers() == Qt::ControlModifier) || (e->modifiers() == (Qt::ShiftModifier | Qt::ControlModifier))) {
3369         return false;
3370     }
3371 
3372     // printable or private use is good, see e.g. bug 366424 (typing "private use" unicode characters)
3373     return c.isPrint() || (c.category() == QChar::Other_PrivateUse);
3374 }
3375 
3376 void KateViewInternal::contextMenuEvent(QContextMenuEvent *e)
3377 {
3378     // calculate where to show the context menu
3379 
3380     QPoint p = e->pos();
3381 
3382     if (e->reason() == QContextMenuEvent::Keyboard) {
3383         makeVisible(m_displayCursor, 0);
3384         p = cursorCoordinates(false);
3385         p.rx() -= startX();
3386     } else if (!view()->selection() || view()->config()->persistentSelection()) {
3387         placeCursor(e->pos());
3388     }
3389 
3390     // show it
3391     QMenu *cm = view()->contextMenu();
3392     if (cm) {
3393         view()->spellingMenu()->prepareToBeShown(cm);
3394         cm->popup(mapToGlobal(p));
3395         e->accept();
3396     }
3397 }
3398 
3399 void KateViewInternal::mousePressEvent(QMouseEvent *e)
3400 {
3401     if (sendMouseEventToInputContext(e)) {
3402         return;
3403     }
3404 
3405     // was an inline note clicked?
3406     const auto noteData = inlineNoteAt(e->globalPosition().toPoint());
3407     const KTextEditor::InlineNote note(noteData);
3408     if (note.position().isValid()) {
3409         note.provider()->inlineNoteActivated(noteData, e->button(), e->globalPosition().toPoint());
3410         return;
3411     }
3412 
3413     commitPreedit();
3414 
3415     // no -- continue with normal handling
3416     switch (e->button()) {
3417     case Qt::LeftButton:
3418 
3419         m_selChangedByUser = false;
3420 
3421         if (!view()->isMulticursorNotAllowed() && e->modifiers() == view()->config()->multiCursorModifiers()) {
3422             auto pos = cursorForPoint(e->pos());
3423             if (pos.isValid()) {
3424                 view()->addSecondaryCursor(pos);
3425                 e->accept();
3426                 return;
3427             }
3428         } else {
3429             view()->clearSecondaryCursors();
3430         }
3431 
3432         if (m_possibleTripleClick) {
3433             m_possibleTripleClick = false;
3434 
3435             m_selectionMode = Line;
3436 
3437             if (e->modifiers() & Qt::ShiftModifier) {
3438                 updateSelection(m_cursor, true);
3439             } else {
3440                 view()->selectLine(m_cursor);
3441                 if (view()->selection()) {
3442                     m_selectAnchor = view()->selectionRange().start();
3443                 }
3444             }
3445 
3446             if (view()->selection()) {
3447                 QApplication::clipboard()->setText(view()->selectionText(), QClipboard::Selection);
3448             }
3449 
3450             // Keep the line at the select anchor selected during further
3451             // mouse selection
3452             if (m_selectAnchor.line() > view()->selectionRange().start().line()) {
3453                 // Preserve the last selected line
3454                 if (m_selectAnchor == view()->selectionRange().end() && m_selectAnchor.column() == 0) {
3455                     m_selectionCached.setStart(KTextEditor::Cursor(m_selectAnchor.line() - 1, 0));
3456                 } else {
3457                     m_selectionCached.setStart(KTextEditor::Cursor(m_selectAnchor.line(), 0));
3458                 }
3459                 m_selectionCached.setEnd(view()->selectionRange().end());
3460             } else {
3461                 // Preserve the first selected line
3462                 m_selectionCached.setStart(view()->selectionRange().start());
3463                 if (view()->selectionRange().end().line() > view()->selectionRange().start().line()) {
3464                     m_selectionCached.setEnd(KTextEditor::Cursor(view()->selectionRange().start().line() + 1, 0));
3465                 } else {
3466                     m_selectionCached.setEnd(view()->selectionRange().end());
3467                 }
3468             }
3469 
3470             moveCursorToSelectionEdge();
3471 
3472             m_scrollX = 0;
3473             m_scrollY = 0;
3474             m_scrollTimer.start(50);
3475 
3476             e->accept();
3477             return;
3478         } else if (m_selectionMode == Default) {
3479             m_selectionMode = Mouse;
3480         }
3481 
3482         // request the software keyboard, if any
3483         if (e->button() == Qt::LeftButton && qApp->autoSipEnabled()) {
3484             QStyle::RequestSoftwareInputPanel behavior = QStyle::RequestSoftwareInputPanel(style()->styleHint(QStyle::SH_RequestSoftwareInputPanel));
3485             if (hasFocus() || behavior == QStyle::RSIP_OnMouseClick) {
3486                 QEvent event(QEvent::RequestSoftwareInputPanel);
3487                 QApplication::sendEvent(this, &event);
3488             }
3489         }
3490 
3491         if (e->modifiers() & Qt::ShiftModifier) {
3492             if (!m_selectAnchor.isValid()) {
3493                 m_selectAnchor = m_cursor;
3494             }
3495         } else {
3496             m_selectionCached = KTextEditor::Range::invalid();
3497         }
3498 
3499         if (view()->config()->textDragAndDrop() && !(e->modifiers() & Qt::ShiftModifier) && isTargetSelected(e->pos())) {
3500             m_dragInfo.state = diPending;
3501             m_dragInfo.start = e->pos();
3502         } else {
3503             m_dragInfo.state = diNone;
3504 
3505             if (e->modifiers() & Qt::ShiftModifier) {
3506                 placeCursor(e->pos(), true, false);
3507                 if (m_selectionCached.start().isValid()) {
3508                     if (m_cursor.toCursor() < m_selectionCached.start()) {
3509                         m_selectAnchor = m_selectionCached.end();
3510                     } else {
3511                         m_selectAnchor = m_selectionCached.start();
3512                     }
3513                 }
3514 
3515                 // update selection and do potential clipboard update, see bug 443642
3516                 setSelection(KTextEditor::Range(m_selectAnchor, m_cursor));
3517                 if (view()->selection()) {
3518                     QApplication::clipboard()->setText(view()->selectionText(), QClipboard::Selection);
3519                 }
3520             } else {
3521                 placeCursor(e->pos());
3522             }
3523 
3524             m_scrollX = 0;
3525             m_scrollY = 0;
3526 
3527             m_scrollTimer.start(50);
3528         }
3529 
3530         e->accept();
3531         break;
3532 
3533     case Qt::RightButton:
3534         if (e->pos().x() == 0) {
3535             // Special handling for folding by right click
3536             placeCursor(e->pos());
3537             e->accept();
3538         }
3539         break;
3540 
3541     default:
3542         e->ignore();
3543         break;
3544     }
3545 }
3546 
3547 void KateViewInternal::mouseDoubleClickEvent(QMouseEvent *e)
3548 {
3549     if (sendMouseEventToInputContext(e)) {
3550         return;
3551     }
3552     if (e->button() == Qt::LeftButton) {
3553         m_selectionMode = Word;
3554 
3555         if (e->modifiers() & Qt::ShiftModifier) {
3556             // Now select the word under the select anchor
3557             int cs;
3558             int ce;
3559             Kate::TextLine l = doc()->kateTextLine(m_selectAnchor.line());
3560 
3561             ce = m_selectAnchor.column();
3562             if (ce > 0 && doc()->highlight()->isInWord(l.at(ce))) {
3563                 for (; ce < l.length(); ce++) {
3564                     if (!doc()->highlight()->isInWord(l.at(ce))) {
3565                         break;
3566                     }
3567                 }
3568             }
3569 
3570             cs = m_selectAnchor.column() - 1;
3571             if (cs < doc()->lineLength(m_selectAnchor.line()) && doc()->highlight()->isInWord(l.at(cs))) {
3572                 for (cs--; cs >= 0; cs--) {
3573                     if (!doc()->highlight()->isInWord(l.at(cs))) {
3574                         break;
3575                     }
3576                 }
3577             }
3578 
3579             // ...and keep it selected
3580             if (cs + 1 < ce) {
3581                 m_selectionCached.setStart(KTextEditor::Cursor(m_selectAnchor.line(), cs + 1));
3582                 m_selectionCached.setEnd(KTextEditor::Cursor(m_selectAnchor.line(), ce));
3583             } else {
3584                 m_selectionCached.setStart(m_selectAnchor);
3585                 m_selectionCached.setEnd(m_selectAnchor);
3586             }
3587             // Now word select to the mouse cursor
3588             placeCursor(e->pos(), true);
3589         } else {
3590             // first clear the selection, otherwise we run into bug #106402
3591             // ...and set the cursor position, for the same reason (otherwise there
3592             // are *other* idiosyncrasies we can't fix without reintroducing said
3593             // bug)
3594             // Parameters: don't redraw, and don't emit selectionChanged signal yet
3595             view()->clearSelection(false, false);
3596             placeCursor(e->pos());
3597             view()->selectWord(m_cursor);
3598             cursorToMatchingBracket(true);
3599 
3600             if (view()->selection()) {
3601                 m_selectAnchor = view()->selectionRange().start();
3602                 m_selectionCached = view()->selectionRange();
3603             } else {
3604                 m_selectAnchor = m_cursor;
3605                 m_selectionCached = KTextEditor::Range(m_cursor, m_cursor);
3606             }
3607         }
3608 
3609         // Move cursor to end (or beginning) of selected word
3610 #ifndef Q_OS_MACOS
3611         if (view()->selection()) {
3612             QApplication::clipboard()->setText(view()->selectionText(), QClipboard::Selection);
3613         }
3614 #endif
3615 
3616         moveCursorToSelectionEdge();
3617         m_possibleTripleClick = true;
3618         QTimer::singleShot(QApplication::doubleClickInterval(), this, SLOT(tripleClickTimeout()));
3619 
3620         m_scrollX = 0;
3621         m_scrollY = 0;
3622 
3623         m_scrollTimer.start(50);
3624 
3625         e->accept();
3626     } else {
3627         e->ignore();
3628     }
3629 }
3630 
3631 void KateViewInternal::tripleClickTimeout()
3632 {
3633     m_possibleTripleClick = false;
3634 }
3635 
3636 void KateViewInternal::beginSelectLine(const QPoint &pos)
3637 {
3638     placeCursor(pos);
3639     m_possibleTripleClick = true; // set so subsequent mousePressEvent will select line
3640 }
3641 
3642 void KateViewInternal::mouseReleaseEvent(QMouseEvent *e)
3643 {
3644     if (sendMouseEventToInputContext(e)) {
3645         return;
3646     }
3647 
3648     switch (e->button()) {
3649     case Qt::LeftButton:
3650         m_selectionMode = Default;
3651         //       m_selectionCached.start().setLine( -1 );
3652 
3653         if (m_selChangedByUser) {
3654             if (view()->selection()) {
3655                 QApplication::clipboard()->setText(view()->selectionText(), QClipboard::Selection);
3656             }
3657             moveCursorToSelectionEdge();
3658 
3659             m_selChangedByUser = false;
3660         }
3661 
3662         if (m_dragInfo.state == diPending) {
3663             placeCursor(e->pos(), e->modifiers() & Qt::ShiftModifier);
3664         } else if (m_dragInfo.state == diNone) {
3665             m_scrollTimer.stop();
3666         }
3667 
3668         m_dragInfo.state = diNone;
3669 
3670         // merge any overlapping selections/cursors
3671         if (view()->selection() && !view()->m_secondaryCursors.empty()) {
3672             mergeSelections();
3673         }
3674 
3675         e->accept();
3676         break;
3677 
3678     case Qt::MiddleButton:
3679         if (!view()->config()->mousePasteAtCursorPosition()) {
3680             placeCursor(e->pos());
3681         }
3682 
3683         if (doc()->isReadWrite()) {
3684             QString clipboard = QApplication::clipboard()->text(QClipboard::Selection);
3685             view()->paste(&clipboard);
3686         }
3687 
3688         e->accept();
3689         break;
3690 
3691     default:
3692         e->ignore();
3693         break;
3694     }
3695 }
3696 
3697 void KateViewInternal::leaveEvent(QEvent *)
3698 {
3699     m_textHintTimer.stop();
3700 
3701     // fix bug 194452, scrolling keeps going if you scroll via mouse drag and press and other mouse
3702     // button outside the view area
3703     if (m_dragInfo.state == diNone) {
3704         m_scrollTimer.stop();
3705     }
3706 
3707     hideBracketMatchPreview();
3708 }
3709 
3710 KTextEditor::Cursor KateViewInternal::coordinatesToCursor(const QPoint &_coord, bool includeBorder) const
3711 {
3712     QPoint coord(_coord);
3713 
3714     KTextEditor::Cursor ret = KTextEditor::Cursor::invalid();
3715 
3716     if (includeBorder) {
3717         coord.rx() -= m_leftBorder->width();
3718     }
3719     coord.rx() += startX();
3720 
3721     const KateTextLayout &thisLine = yToKateTextLayout(coord.y());
3722     if (thisLine.isValid()) {
3723         ret = renderer()->xToCursor(thisLine, coord.x(), !view()->wrapCursor());
3724     }
3725 
3726     if (ret.column() > view()->document()->lineLength(ret.line())) {
3727         // The cursor is beyond the end of the line; in that case the renderer
3728         // gives the index of the character behind the last one.
3729         return KTextEditor::Cursor::invalid();
3730     }
3731 
3732     return ret;
3733 }
3734 
3735 void KateViewInternal::mouseMoveEvent(QMouseEvent *e)
3736 {
3737     if (m_scroller->state() != QScroller::Inactive) {
3738         // Touchscreen is handled by scrollEvent()
3739         return;
3740     }
3741     KTextEditor::Cursor newPosition = coordinatesToCursor(e->pos(), false);
3742     if (newPosition != m_mouse) {
3743         m_mouse = newPosition;
3744         mouseMoved();
3745     }
3746 
3747     if (e->buttons() == Qt::NoButton) {
3748         auto noteData = inlineNoteAt(e->globalPosition().toPoint());
3749         auto focusChanged = false;
3750         if (noteData.m_position.isValid()) {
3751             if (!m_activeInlineNote.m_position.isValid()) {
3752                 // no active note -- focus in
3753                 tagLine(noteData.m_position);
3754                 focusChanged = true;
3755                 noteData.m_underMouse = true;
3756                 noteData.m_provider->inlineNoteFocusInEvent(KTextEditor::InlineNote(noteData), e->globalPosition().toPoint());
3757                 m_activeInlineNote = noteData;
3758             } else {
3759                 noteData.m_provider->inlineNoteMouseMoveEvent(KTextEditor::InlineNote(noteData), e->globalPosition().toPoint());
3760             }
3761         } else if (m_activeInlineNote.m_position.isValid()) {
3762             tagLine(m_activeInlineNote.m_position);
3763             focusChanged = true;
3764             m_activeInlineNote.m_underMouse = false;
3765             m_activeInlineNote.m_provider->inlineNoteFocusOutEvent(KTextEditor::InlineNote(m_activeInlineNote));
3766             m_activeInlineNote = {};
3767         }
3768         if (focusChanged) {
3769             // the note might change its appearance in reaction to the focus event
3770             updateDirty();
3771         }
3772     }
3773 
3774     if (e->buttons() & Qt::LeftButton) {
3775         if (m_dragInfo.state == diPending) {
3776             // we had a mouse down, but haven't confirmed a drag yet
3777             // if the mouse has moved sufficiently, we will confirm
3778             QPoint p(e->pos() - m_dragInfo.start);
3779 
3780             // we've left the drag square, we can start a real drag operation now
3781             if (p.manhattanLength() > QApplication::startDragDistance()) {
3782                 doDrag();
3783             }
3784 
3785             return;
3786         } else if (m_dragInfo.state == diDragging) {
3787             // Don't do anything after a canceled drag until the user lets go of
3788             // the mouse button!
3789             return;
3790         }
3791 
3792         m_mouseX = e->position().x();
3793         m_mouseY = e->position().y();
3794 
3795         m_scrollX = 0;
3796         m_scrollY = 0;
3797         int d = renderer()->lineHeight();
3798 
3799         if (m_mouseX < 0) {
3800             m_scrollX = -d;
3801         }
3802 
3803         if (m_mouseX > width()) {
3804             m_scrollX = d;
3805         }
3806 
3807         if (m_mouseY < 0) {
3808             m_mouseY = 0;
3809             m_scrollY = -d;
3810         }
3811 
3812         if (m_mouseY > height()) {
3813             m_mouseY = height();
3814             m_scrollY = d;
3815         }
3816 
3817         if (!m_scrollY) {
3818             // We are on top of line number area and dragging
3819             // Since we never get negative y, ensure to scroll up
3820             // a bit otherwise we get stuck on the topview line
3821             if (!m_mouseY) {
3822                 m_scrollY -= d;
3823             }
3824             placeCursor(QPoint(m_mouseX, m_mouseY), true);
3825         }
3826     } else {
3827         if (view()->config()->textDragAndDrop() && isTargetSelected(e->pos())) {
3828             // mouse is over selected text. indicate that the text is draggable by setting
3829             // the arrow cursor as other Qt text editing widgets do
3830             if (m_mouseCursor != Qt::ArrowCursor) {
3831                 m_mouseCursor = Qt::ArrowCursor;
3832                 setCursor(m_mouseCursor);
3833             }
3834         } else {
3835             // normal text cursor
3836             if (m_mouseCursor != Qt::IBeamCursor) {
3837                 m_mouseCursor = Qt::IBeamCursor;
3838                 setCursor(m_mouseCursor);
3839             }
3840         }
3841         // We need to check whether the mouse position is actually within the widget,
3842         // because other widgets like the icon border forward their events to this,
3843         // and we will create invalid text hint requests if we don't check
3844         if (textHintsEnabled() && geometry().contains(parentWidget()->mapFromGlobal(e->globalPosition()).toPoint())) {
3845             if (QToolTip::isVisible()) {
3846                 QToolTip::hideText();
3847             }
3848             m_textHintTimer.start(m_textHintDelay);
3849             m_textHintPos = e->pos();
3850         }
3851     }
3852 }
3853 
3854 void KateViewInternal::updateDirty()
3855 {
3856     const int h = renderer()->lineHeight();
3857 
3858     int currentRectStart = -1;
3859     int currentRectEnd = -1;
3860 
3861     QRegion updateRegion;
3862 
3863     {
3864         for (int i = 0; i < cache()->viewCacheLineCount(); ++i) {
3865             if (cache()->viewLine(i).isDirty()) {
3866                 if (currentRectStart == -1) {
3867                     currentRectStart = h * i;
3868                     currentRectEnd = h;
3869                 } else {
3870                     currentRectEnd += h;
3871                 }
3872 
3873             } else if (currentRectStart != -1) {
3874                 updateRegion += QRect(0, currentRectStart, width(), currentRectEnd);
3875                 currentRectStart = -1;
3876                 currentRectEnd = -1;
3877             }
3878         }
3879     }
3880 
3881     if (currentRectStart != -1) {
3882         updateRegion += QRect(0, currentRectStart, width(), currentRectEnd);
3883     }
3884 
3885     if (!updateRegion.isEmpty()) {
3886         if (debugPainting) {
3887             qCDebug(LOG_KTE) << "Update dirty region " << updateRegion;
3888         }
3889         update(updateRegion);
3890     }
3891 }
3892 
3893 void KateViewInternal::hideEvent(QHideEvent *e)
3894 {
3895     Q_UNUSED(e);
3896     if (view()->isCompletionActive()) {
3897         view()->completionWidget()->abortCompletion();
3898     }
3899 }
3900 
3901 void KateViewInternal::paintEvent(QPaintEvent *e)
3902 {
3903     if (debugPainting) {
3904         qCDebug(LOG_KTE) << "GOT PAINT EVENT: Region" << e->region();
3905     }
3906 
3907     const QRect &unionRect = e->rect();
3908 
3909     int xStart = startX() + unionRect.x();
3910     int xEnd = xStart + unionRect.width();
3911     uint h = renderer()->lineHeight();
3912     uint startz = (unionRect.y() / h);
3913     uint endz = startz + 1 + (unionRect.height() / h);
3914     uint lineRangesSize = cache()->viewCacheLineCount();
3915     const KTextEditor::Cursor pos = m_cursor;
3916 
3917     QPainter paint(this);
3918 
3919     // THIS IS ULTRA EVIL AND ADDS STRANGE RENDERING ARTIFACTS WITH SCALING!!!!
3920     // SEE BUG https://bugreports.qt.io/browse/QTBUG-66036
3921     // paint.setRenderHints(QPainter::TextAntialiasing);
3922 
3923     paint.save();
3924 
3925     renderer()->setCaretStyle(m_currentInputMode->caretStyle());
3926     renderer()->setShowTabs(doc()->config()->showTabs());
3927     renderer()->setShowSpaces(doc()->config()->showSpaces());
3928     renderer()->updateMarkerSize();
3929 
3930     // paint line by line
3931     // this includes parts that span areas without real lines
3932     // translate to first line to paint
3933     paint.translate(unionRect.x(), startz * h);
3934     for (uint z = startz; z <= endz; z++) {
3935         // paint regions without lines mapped to
3936         if ((z >= lineRangesSize) || (cache()->viewLine(z).line() == -1)) {
3937             if (!(z >= lineRangesSize)) {
3938                 cache()->viewLine(z).setDirty(false);
3939             }
3940             paint.fillRect(0, 0, unionRect.width(), h, m_view->rendererConfig()->backgroundColor());
3941         }
3942 
3943         // paint text lines
3944         else {
3945             // If viewLine() returns non-zero, then a document line was split
3946             // in several visual lines, and we're trying to paint visual line
3947             // that is not the first.  In that case, this line was already
3948             // painted previously, since KateRenderer::paintTextLine paints
3949             // all visual lines.
3950             //
3951             // Except if we're at the start of the region that needs to
3952             // be painted -- when no previous calls to paintTextLine were made.
3953             KateTextLayout &thisLine = cache()->viewLine(z);
3954             if (!thisLine.viewLine() || z == startz) {
3955                 // paint our line
3956                 // set clipping region to only paint the relevant parts
3957                 paint.save();
3958                 const int thisLineTop = h * thisLine.viewLine();
3959                 paint.translate(QPoint(0, -thisLineTop));
3960 
3961                 // compute rect for line, fill the stuff
3962                 // important: as we allow some ARGB colors for other stuff, it is REALLY important to fill the full range once!
3963                 const QRectF lineRect(0, 0, unionRect.width(), h * thisLine.kateLineLayout()->viewLineCount());
3964                 paint.fillRect(lineRect, m_view->rendererConfig()->backgroundColor());
3965 
3966                 // THIS IS ULTRA EVIL AND ADDS STRANGE RENDERING ARTIFACTS WITH SCALING!!!!
3967                 // SEE BUG https://bugreports.qt.io/browse/QTBUG-66036
3968                 // => using a QRectF solves the cut of 1 pixel, the same call with QRect does create artifacts!
3969                 paint.setClipRect(lineRect);
3970 
3971                 // QTextLayout::draw does not take into account QPainter's viewport, so it will try to render
3972                 // lines outside visible bounds. This causes a significant slowdown when a very long line
3973                 // dynamically broken into multiple lines. To avoid this, an explicit text clip rect is set.
3974                 const QRect textClipRect{xStart, thisLineTop, xEnd - xStart, height()};
3975 
3976                 renderer()->paintTextLine(paint, thisLine.kateLineLayout(), xStart, xEnd, textClipRect.toRectF(), &pos);
3977                 paint.restore();
3978 
3979                 // line painted, reset and state + mark line as non-dirty
3980                 thisLine.setDirty(false);
3981             }
3982         }
3983 
3984         // translate to next line
3985         paint.translate(0, h);
3986     }
3987 
3988     paint.restore();
3989 
3990     if (m_textAnimation) {
3991         m_textAnimation->draw(paint);
3992     }
3993 }
3994 
3995 void KateViewInternal::resizeEvent(QResizeEvent *e)
3996 {
3997     bool expandedHorizontally = width() > e->oldSize().width();
3998     bool expandedVertically = height() > e->oldSize().height();
3999     bool heightChanged = height() != e->oldSize().height();
4000 
4001     m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height());
4002     m_madeVisible = false;
4003 
4004     // resize the bracket match preview
4005     if (m_bmPreview) {
4006         showBracketMatchPreview();
4007     }
4008 
4009     if (heightChanged) {
4010         setAutoCenterLines(m_autoCenterLines, false);
4011         m_cachedMaxStartPos.setPosition(-1, -1);
4012     }
4013 
4014     if (view()->dynWordWrap()) {
4015         bool dirtied = false;
4016 
4017         for (int i = 0; i < cache()->viewCacheLineCount(); i++) {
4018             // find the first dirty line
4019             // the word wrap updateView algorithm is forced to check all lines after a dirty one
4020             KateTextLayout viewLine = cache()->viewLine(i);
4021 
4022             if (viewLine.wrap() || viewLine.isRightToLeft() || viewLine.width() > width()) {
4023                 dirtied = true;
4024                 viewLine.setDirty();
4025                 break;
4026             }
4027         }
4028 
4029         if (dirtied || heightChanged) {
4030             updateView(true);
4031             m_leftBorder->update();
4032         }
4033     } else {
4034         updateView();
4035 
4036         if (expandedHorizontally && startX() > 0) {
4037             scrollColumns(startX() - (width() - e->oldSize().width()));
4038         }
4039     }
4040 
4041     if (width() < e->oldSize().width() && !view()->wrapCursor()) {
4042         // May have to restrain cursor to new smaller width...
4043         if (m_cursor.column() > doc()->lineLength(m_cursor.line())) {
4044             KateTextLayout thisLine = currentLayout(m_cursor);
4045 
4046             KTextEditor::Cursor newCursor(m_cursor.line(),
4047                                           thisLine.endCol() + ((width() - thisLine.xOffset() - (thisLine.width() - startX())) / renderer()->spaceWidth()) - 1);
4048             if (newCursor.column() < m_cursor.column()) {
4049                 updateCursor(newCursor);
4050             }
4051         }
4052     }
4053 
4054     if (expandedVertically) {
4055         KTextEditor::Cursor max = maxStartPos();
4056         if (startPos() > max) {
4057             scrollPos(max);
4058             return; // already fired displayRangeChanged
4059         }
4060     }
4061     Q_EMIT view()->displayRangeChanged(m_view);
4062 }
4063 
4064 void KateViewInternal::moveEvent(QMoveEvent *e)
4065 {
4066     // move the bracket match preview to the new location
4067     if (e->pos() != e->oldPos() && m_bmPreview) {
4068         showBracketMatchPreview();
4069     }
4070 
4071     QWidget::moveEvent(e);
4072 }
4073 
4074 void KateViewInternal::scrollTimeout()
4075 {
4076     if (m_scrollX || m_scrollY) {
4077         const int scrollTo = startPos().line() + (m_scrollY / (int)renderer()->lineHeight());
4078         placeCursor(QPoint(m_mouseX, m_mouseY), true);
4079         scrollLines(scrollTo);
4080     }
4081 }
4082 
4083 void KateViewInternal::cursorTimeout()
4084 {
4085     if (!debugPainting && m_currentInputMode->blinkCaret()) {
4086         renderer()->setDrawCaret(!renderer()->drawCaret());
4087         paintCursor();
4088     }
4089 }
4090 
4091 void KateViewInternal::textHintTimeout()
4092 {
4093     m_textHintTimer.stop();
4094 
4095     KTextEditor::Cursor c = coordinatesToCursor(m_textHintPos, false);
4096     if (!c.isValid()) {
4097         return;
4098     }
4099 
4100     QStringList textHints;
4101     for (KTextEditor::TextHintProvider *const p : m_textHintProviders) {
4102         if (!p) {
4103             continue;
4104         }
4105 
4106         const QString hint = p->textHint(m_view, c);
4107         if (!hint.isEmpty()) {
4108             textHints.append(hint);
4109         }
4110     }
4111 
4112     if (!textHints.isEmpty()) {
4113         qCDebug(LOG_KTE) << "Hint text: " << textHints;
4114         QString hint;
4115         for (const QString &str : std::as_const(textHints)) {
4116             hint += QStringLiteral("<p>%1</p>").arg(str);
4117         }
4118         QPoint pos(startX() + m_textHintPos.x(), m_textHintPos.y());
4119         QToolTip::showText(mapToGlobal(pos), hint);
4120     }
4121 }
4122 
4123 void KateViewInternal::focusInEvent(QFocusEvent *)
4124 {
4125     if (QApplication::cursorFlashTime() > 0) {
4126         m_cursorTimer.start(QApplication::cursorFlashTime() / 2);
4127     }
4128 
4129     paintCursor();
4130 
4131     doc()->setActiveView(m_view);
4132 
4133     // this will handle focus stuff in kateview
4134     view()->slotGotFocus();
4135 }
4136 
4137 void KateViewInternal::focusOutEvent(QFocusEvent *)
4138 {
4139     // if (view()->isCompletionActive())
4140     // view()->abortCompletion();
4141 
4142     m_cursorTimer.stop();
4143     view()->renderer()->setDrawCaret(true);
4144     paintCursor();
4145 
4146     m_textHintTimer.stop();
4147 
4148     view()->slotLostFocus();
4149 
4150     hideBracketMatchPreview();
4151 }
4152 
4153 void KateViewInternal::doDrag()
4154 {
4155     m_dragInfo.state = diDragging;
4156     m_dragInfo.dragObject = new QDrag(this);
4157     std::unique_ptr<QMimeData> mimeData(new QMimeData());
4158     mimeData->setText(view()->selectionText());
4159 
4160     const auto startCur = view()->selectionRange().start();
4161     const auto endCur = view()->selectionRange().end();
4162     if (!startCur.isValid() || !endCur.isValid()) {
4163         return;
4164     }
4165 
4166     int startLine = startCur.line();
4167     int endLine = endCur.line();
4168 
4169     /**
4170      * Get real first and last visible line nos.
4171      * This is important as startLine() / endLine() are virtual and we can't use
4172      * them here
4173      */
4174     const int firstVisibleLine = view()->firstDisplayedLineInternal(KTextEditor::View::RealLine);
4175     const int lastVisibleLine = view()->lastDisplayedLineInternal(KTextEditor::View::RealLine);
4176 
4177     // get visible selected lines
4178     for (int l = startLine; l <= endLine; ++l) {
4179         if (l >= firstVisibleLine) {
4180             break;
4181         }
4182         ++startLine;
4183     }
4184     for (int l = endLine; l >= startLine; --l) {
4185         if (l <= lastVisibleLine) {
4186             break;
4187         }
4188         --endLine;
4189     }
4190 
4191     // calculate the height / width / scale
4192     int w = 0;
4193     int h = 0;
4194     const QFontMetricsF &fm = renderer()->currentFontMetrics();
4195     for (int l = startLine; l <= endLine; ++l) {
4196         w = std::max((int)fm.horizontalAdvance(doc()->line(l)), w);
4197         h += renderer()->lineHeight();
4198     }
4199     qreal scale = h > m_view->height() / 2 ? 0.75 : 1.0;
4200 
4201     // Calculate start x pos on start line
4202     int sX = 0;
4203     if (startLine == startCur.line()) {
4204         sX = renderer()->cursorToX(cache()->textLayout(startCur), startCur, !view()->wrapCursor());
4205     }
4206 
4207     // Calculate end x pos on end line
4208     int eX = 0;
4209     if (endLine == endCur.line()) {
4210         eX = renderer()->cursorToX(cache()->textLayout(endCur), endCur, !view()->wrapCursor());
4211     }
4212 
4213     // These are the occurunce highlights, the ones you get when you select a word
4214     // We clear them here so that they don't come up in the dragged pixmap
4215     // After we are done creating the pixmap, we restore them
4216     if (view()->selection()) {
4217         view()->clearHighlights();
4218     }
4219 
4220     // Create a pixmap this selection
4221     const qreal dpr = devicePixelRatioF();
4222     QPixmap pixmap(w * dpr, h * dpr);
4223     if (!pixmap.isNull()) {
4224         pixmap.setDevicePixelRatio(dpr);
4225         pixmap.fill(Qt::transparent);
4226         renderer()->paintSelection(&pixmap, startLine, sX, endLine, eX, scale);
4227 
4228         if (view()->selection()) {
4229             // Tell the view to restore the highlights
4230             Q_EMIT view()->selectionChanged(view());
4231         }
4232     }
4233 
4234     // Calculate position where pixmap will appear when user
4235     // starts dragging
4236     const int x = 0;
4237     /**
4238      * lineToVisibleLine() = real line => virtual line
4239      * This is necessary here because if there is a folding in the current
4240      * view lines, the y pos can be incorrect. So, we make sure to convert
4241      * it to virtual line before calculating y
4242      */
4243     const int y = lineToY(view()->m_textFolding.lineToVisibleLine(startLine));
4244     const QPoint pos = mapFromGlobal(QCursor::pos()) - QPoint(x, y);
4245 
4246     m_dragInfo.dragObject->setPixmap(pixmap);
4247     m_dragInfo.dragObject->setHotSpot(pos);
4248     m_dragInfo.dragObject->setMimeData(mimeData.release());
4249     m_dragInfo.dragObject->exec(Qt::MoveAction | Qt::CopyAction);
4250 }
4251 
4252 void KateViewInternal::dragEnterEvent(QDragEnterEvent *event)
4253 {
4254     if (event->source() == this) {
4255         event->setDropAction(Qt::MoveAction);
4256     }
4257     event->setAccepted((event->mimeData()->hasText() && doc()->isReadWrite()) || event->mimeData()->hasUrls());
4258 }
4259 
4260 void KateViewInternal::fixDropEvent(QDropEvent *event)
4261 {
4262     if (event->source() != this) {
4263         event->setDropAction(Qt::CopyAction);
4264     } else {
4265         Qt::DropAction action = Qt::MoveAction;
4266 #ifdef Q_WS_MAC
4267         if (event->modifiers() & Qt::AltModifier) {
4268             action = Qt::CopyAction;
4269         }
4270 #else
4271         if (event->modifiers() & Qt::ControlModifier) {
4272             action = Qt::CopyAction;
4273         }
4274 #endif
4275         event->setDropAction(action);
4276     }
4277 }
4278 
4279 void KateViewInternal::dragMoveEvent(QDragMoveEvent *event)
4280 {
4281     // track the cursor to the current drop location
4282     placeCursor(event->position().toPoint(), true, false);
4283 
4284     // important: accept action to switch between copy and move mode
4285     // without this, the text will always be copied.
4286     fixDropEvent(event);
4287 }
4288 
4289 void KateViewInternal::dropEvent(QDropEvent *event)
4290 {
4291     // if we have urls, pass this event off to the hosting application
4292     if (event->mimeData()->hasUrls()) {
4293         Q_EMIT dropEventPass(event);
4294         return;
4295     }
4296 
4297     if (event->mimeData()->hasText() && doc()->isReadWrite()) {
4298         const QString text = event->mimeData()->text();
4299         const bool blockMode = view()->blockSelection();
4300 
4301         fixDropEvent(event);
4302 
4303         // Remember where to paste/move...
4304         KTextEditor::Cursor targetCursor(m_cursor);
4305         // Use powerful MovingCursor to track our changes we may do
4306         std::unique_ptr<KTextEditor::MovingCursor> targetCursor2(doc()->newMovingCursor(m_cursor));
4307 
4308         // As always need the BlockMode some special treatment
4309         const KTextEditor::Range selRange(view()->selectionRange());
4310         const KTextEditor::Cursor blockAdjust(selRange.numberOfLines(), selRange.columnWidth());
4311 
4312         // Restore the cursor position before editStart(), so that it is correctly stored for the undo action
4313         if (event->dropAction() != Qt::CopyAction) {
4314             editSetCursor(selRange.end());
4315         } else {
4316             view()->clearSelection();
4317         }
4318 
4319         // use one transaction
4320         doc()->editStart();
4321 
4322         if (event->dropAction() != Qt::CopyAction) {
4323             view()->removeSelectedText();
4324             if (targetCursor2->toCursor() != targetCursor) {
4325                 // Hm, multi line selection moved down, we need to adjust our dumb cursor
4326                 targetCursor = targetCursor2->toCursor();
4327             }
4328             doc()->insertText(targetCursor2->toCursor(), text, blockMode);
4329 
4330         } else {
4331             doc()->insertText(targetCursor, text, blockMode);
4332         }
4333 
4334         if (blockMode) {
4335             setSelection(KTextEditor::Range(targetCursor, targetCursor + blockAdjust));
4336             editSetCursor(targetCursor + blockAdjust);
4337         } else {
4338             setSelection(KTextEditor::Range(targetCursor, targetCursor2->toCursor()));
4339             editSetCursor(targetCursor2->toCursor()); // Just to satisfy autotest
4340         }
4341 
4342         doc()->editEnd();
4343 
4344         event->acceptProposedAction();
4345         updateView();
4346     }
4347 
4348     // finally finish drag and drop mode
4349     m_dragInfo.state = diNone;
4350     // important, because the eventFilter`s DragLeave does not occur
4351     stopDragScroll();
4352 }
4353 // END EVENT HANDLING STUFF
4354 
4355 void KateViewInternal::clear()
4356 {
4357     m_startPos.setPosition(0, 0);
4358     m_displayCursor = KTextEditor::Cursor(0, 0);
4359     m_cursor.setPosition(0, 0);
4360     view()->clearSecondaryCursors();
4361     cache()->clear();
4362     updateView(true);
4363     m_lineScroll->updatePixmap();
4364 }
4365 
4366 void KateViewInternal::wheelEvent(QWheelEvent *e)
4367 {
4368     // check if this event should change the font size (Ctrl pressed, angle reported and not accidentally so)
4369     // Note: if detectZoomingEvent() doesn't unset the ControlModifier we'll get accelerated scrolling.
4370     if (m_zoomEventFilter->detectZoomingEvent(e)) {
4371         if (e->angleDelta().y() > 0) {
4372             slotIncFontSizes(qreal(e->angleDelta().y()) / (qreal)QWheelEvent::DefaultDeltasPerStep);
4373         } else if (e->angleDelta().y() < 0) {
4374             slotDecFontSizes(qreal(-e->angleDelta().y()) / (qreal)QWheelEvent::DefaultDeltasPerStep);
4375         }
4376 
4377         // accept always and be done for zooming
4378         e->accept();
4379         return;
4380     }
4381 
4382     // handle vertical scrolling via the scrollbar
4383     if (e->angleDelta().y() != 0) {
4384         // compute distance
4385         auto sign = m_lineScroll->invertedControls() ? -1 : 1;
4386         auto offset = sign * qreal(e->angleDelta().y()) / 120.0;
4387         if (e->modifiers() & Qt::ShiftModifier) {
4388             const auto pageStep = m_lineScroll->pageStep();
4389             offset = qBound(-pageStep, int(offset * pageStep), pageStep);
4390         } else {
4391             offset *= QApplication::wheelScrollLines();
4392         }
4393 
4394         // handle accumulation
4395         m_accumulatedScroll += offset - int(offset);
4396         const auto extraAccumulated = int(m_accumulatedScroll);
4397         m_accumulatedScroll -= extraAccumulated;
4398 
4399         // do scroll
4400         scrollViewLines(int(offset) + extraAccumulated);
4401         e->accept();
4402     }
4403 
4404     // handle horizontal scrolling via the scrollbar
4405     if (e->angleDelta().x() != 0) {
4406         // if we have dyn word wrap, we should ignore the scroll events
4407         if (view()->dynWordWrap()) {
4408             e->accept();
4409             return;
4410         }
4411 
4412         // if we scroll up/down we do not want to trigger unintended sideways scrolls
4413         if (qAbs(e->angleDelta().y()) > qAbs(e->angleDelta().x())) {
4414             e->accept();
4415             return;
4416         }
4417 
4418         if (QApplication::sendEvent(m_columnScroll, e)) {
4419             e->accept();
4420         }
4421     }
4422 
4423     // hide bracket match preview so that it won't linger while scrolling'
4424     hideBracketMatchPreview();
4425 }
4426 
4427 void KateViewInternal::scrollPrepareEvent(QScrollPrepareEvent *event)
4428 {
4429     int lineHeight = renderer()->lineHeight();
4430     event->setViewportSize(QSizeF(0.0, 0.0));
4431     event->setContentPosRange(QRectF(0.0, 0.0, 0.0, m_lineScroll->maximum() * lineHeight));
4432     event->setContentPos(QPointF(0.0, m_lineScroll->value() * lineHeight));
4433     event->accept();
4434 }
4435 
4436 void KateViewInternal::scrollEvent(QScrollEvent *event)
4437 {
4438     // FIXME Add horizontal scrolling, overscroll, scroll between lines, and word wrap awareness
4439     KTextEditor::Cursor newPos((int)event->contentPos().y() / renderer()->lineHeight(), 0);
4440     scrollPos(newPos);
4441     event->accept();
4442 }
4443 
4444 void KateViewInternal::startDragScroll()
4445 {
4446     if (!m_dragScrollTimer.isActive()) {
4447         m_dragScrollTimer.start(s_scrollTime);
4448     }
4449 }
4450 
4451 void KateViewInternal::stopDragScroll()
4452 {
4453     m_dragScrollTimer.stop();
4454     updateView();
4455 }
4456 
4457 void KateViewInternal::doDragScroll()
4458 {
4459     QPoint p = this->mapFromGlobal(QCursor::pos());
4460 
4461     int dx = 0;
4462     int dy = 0;
4463     if (p.y() < s_scrollMargin) {
4464         dy = p.y() - s_scrollMargin;
4465     } else if (p.y() > height() - s_scrollMargin) {
4466         dy = s_scrollMargin - (height() - p.y());
4467     }
4468 
4469     if (p.x() < s_scrollMargin) {
4470         dx = p.x() - s_scrollMargin;
4471     } else if (p.x() > width() - s_scrollMargin) {
4472         dx = s_scrollMargin - (width() - p.x());
4473     }
4474 
4475     dy /= 4;
4476 
4477     if (dy) {
4478         scrollLines(startLine() + dy);
4479     }
4480 
4481     if (columnScrollingPossible() && dx) {
4482         scrollColumns(qMin(startX() + dx, m_columnScroll->maximum()));
4483     }
4484 
4485     if (!dy && !dx) {
4486         stopDragScroll();
4487     }
4488 }
4489 
4490 void KateViewInternal::registerTextHintProvider(KTextEditor::TextHintProvider *provider)
4491 {
4492     if (std::find(m_textHintProviders.cbegin(), m_textHintProviders.cend(), provider) == m_textHintProviders.cend()) {
4493         m_textHintProviders.push_back(provider);
4494     }
4495 
4496     // we have a client, so start timeout
4497     m_textHintTimer.start(m_textHintDelay);
4498 }
4499 
4500 void KateViewInternal::unregisterTextHintProvider(KTextEditor::TextHintProvider *provider)
4501 {
4502     const auto it = std::find(m_textHintProviders.cbegin(), m_textHintProviders.cend(), provider);
4503     if (it != m_textHintProviders.cend()) {
4504         m_textHintProviders.erase(it);
4505     }
4506 
4507     if (m_textHintProviders.empty()) {
4508         m_textHintTimer.stop();
4509     }
4510 }
4511 
4512 void KateViewInternal::setTextHintDelay(int delay)
4513 {
4514     if (delay <= 0) {
4515         m_textHintDelay = 200; // ms
4516     } else {
4517         m_textHintDelay = delay; // ms
4518     }
4519 }
4520 
4521 int KateViewInternal::textHintDelay() const
4522 {
4523     return m_textHintDelay;
4524 }
4525 
4526 bool KateViewInternal::textHintsEnabled()
4527 {
4528     return !m_textHintProviders.empty();
4529 }
4530 
4531 // BEGIN EDIT STUFF
4532 void KateViewInternal::editStart()
4533 {
4534     editSessionNumber++;
4535 
4536     if (editSessionNumber > 1) {
4537         return;
4538     }
4539 
4540     editIsRunning = true;
4541     editOldCursor = m_cursor;
4542     editOldSelection = view()->selectionRange();
4543 }
4544 
4545 void KateViewInternal::editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom)
4546 {
4547     if (editSessionNumber == 0) {
4548         return;
4549     }
4550 
4551     editSessionNumber--;
4552 
4553     if (editSessionNumber > 0) {
4554         return;
4555     }
4556 
4557     // fix start position, might have moved from column 0
4558     // try to clever calculate the right start column for the tricky dyn word wrap case
4559     int col = 0;
4560     if (view()->dynWordWrap()) {
4561         if (KateLineLayout *layout = cache()->line(startLine())) {
4562             int index = layout->viewLineForColumn(startPos().column());
4563             if (index >= 0 && index < layout->viewLineCount()) {
4564                 col = layout->viewLine(index).startCol();
4565             }
4566         }
4567     }
4568     m_startPos.setPosition(startLine(), col);
4569 
4570     if (tagFrom && (editTagLineStart <= int(view()->textFolding().visibleLineToLine(startLine())))) {
4571         tagAll();
4572     } else {
4573         tagLines(editTagLineStart, tagFrom ? qMax(doc()->lastLine() + 1, editTagLineEnd) : editTagLineEnd, true);
4574     }
4575 
4576     if (editOldCursor == m_cursor.toCursor()) {
4577         updateBracketMarks();
4578     }
4579 
4580     updateView(true);
4581 
4582     if (editOldCursor != m_cursor.toCursor() || m_view == doc()->activeView()) {
4583         // Only scroll the view to the cursor if the insertion happens at the cursor.
4584         // This might not be the case for e.g. collaborative editing, when a remote user
4585         // inserts text at a position not at the caret.
4586         if (m_cursor.line() >= editTagLineStart && m_cursor.line() <= editTagLineEnd) {
4587             m_madeVisible = false;
4588             updateCursor(m_cursor, true);
4589         }
4590     }
4591 
4592     // selection changed?
4593     // fixes bug 316226
4594     if (editOldSelection != view()->selectionRange()
4595         || (editOldSelection.isValid() && !editOldSelection.isEmpty()
4596             && !(editTagLineStart > editOldSelection.end().line() && editTagLineEnd < editOldSelection.start().line()))) {
4597         Q_EMIT view()->selectionChanged(m_view);
4598     }
4599 
4600     editIsRunning = false;
4601 }
4602 
4603 void KateViewInternal::editSetCursor(const KTextEditor::Cursor _cursor)
4604 {
4605     if (m_cursor.toCursor() != _cursor) {
4606         m_cursor.setPosition(_cursor);
4607     }
4608 }
4609 // END
4610 
4611 void KateViewInternal::viewSelectionChanged()
4612 {
4613     if (!view()->selection()) {
4614         m_selectAnchor = KTextEditor::Cursor::invalid();
4615     } else {
4616         const auto r = view()->selectionRange();
4617         m_selectAnchor = r.start() == m_cursor ? r.end() : r.start();
4618     }
4619     // Do NOT nuke the entire range! The reason is that a shift+DC selection
4620     // might (correctly) set the range to be empty (i.e. start() == end()), and
4621     // subsequent dragging might shrink the selection into non-existence. When
4622     // this happens, we use the cached end to restore the cached start so that
4623     // updateSelection is not confused. See also comments in updateSelection.
4624     m_selectionCached.setStart(KTextEditor::Cursor::invalid());
4625     updateMicroFocus();
4626 }
4627 
4628 KateLayoutCache *KateViewInternal::cache() const
4629 {
4630     return m_layoutCache;
4631 }
4632 
4633 KTextEditor::Cursor KateViewInternal::toRealCursor(const KTextEditor::Cursor virtualCursor) const
4634 {
4635     return KTextEditor::Cursor(view()->textFolding().visibleLineToLine(virtualCursor.line()), virtualCursor.column());
4636 }
4637 
4638 KTextEditor::Cursor KateViewInternal::toVirtualCursor(const KTextEditor::Cursor realCursor) const
4639 {
4640     // only convert valid lines, folding doesn't like invalid input!
4641     // don't validate whole cursor, column might be -1
4642     if (realCursor.line() < 0) {
4643         return KTextEditor::Cursor::invalid();
4644     }
4645 
4646     return KTextEditor::Cursor(view()->textFolding().lineToVisibleLine(realCursor.line()), realCursor.column());
4647 }
4648 
4649 KateRenderer *KateViewInternal::renderer() const
4650 {
4651     return view()->renderer();
4652 }
4653 
4654 void KateViewInternal::mouseMoved()
4655 {
4656     view()->notifyMousePositionChanged(m_mouse);
4657     view()->updateRangesIn(KTextEditor::Attribute::ActivateMouseIn);
4658 }
4659 
4660 void KateViewInternal::cursorMoved()
4661 {
4662     view()->updateRangesIn(KTextEditor::Attribute::ActivateCaretIn);
4663 
4664 #ifndef QT_NO_ACCESSIBILITY
4665     if (view()->m_accessibilityEnabled && QAccessible::isActive()) {
4666         QAccessibleTextCursorEvent ev(this, static_cast<KateViewAccessible *>(QAccessible::queryAccessibleInterface(this))->positionFromCursor(this, m_cursor));
4667         QAccessible::updateAccessibility(&ev);
4668     }
4669 #endif
4670 }
4671 
4672 KTextEditor::DocumentPrivate *KateViewInternal::doc()
4673 {
4674     return m_view->doc();
4675 }
4676 
4677 KTextEditor::DocumentPrivate *KateViewInternal::doc() const
4678 {
4679     return m_view->doc();
4680 }
4681 
4682 bool KateViewInternal::rangeAffectsView(KTextEditor::Range range, bool realCursors) const
4683 {
4684     int startLine = KateViewInternal::startLine();
4685     int endLine = startLine + (int)m_visibleLineCount;
4686 
4687     if (realCursors) {
4688         startLine = (int)view()->textFolding().visibleLineToLine(startLine);
4689         endLine = (int)view()->textFolding().visibleLineToLine(endLine);
4690     }
4691 
4692     return (range.end().line() >= startLine) || (range.start().line() <= endLine);
4693 }
4694 
4695 // BEGIN IM INPUT STUFF
4696 QVariant KateViewInternal::inputMethodQuery(Qt::InputMethodQuery query) const
4697 {
4698     switch (query) {
4699     case Qt::ImCursorRectangle: {
4700         // Cursor placement code is changed for Asian input method that
4701         // shows candidate window. This behavior is same as Qt/E 2.3.7
4702         // which supports Asian input methods. Asian input methods need
4703         // start point of IM selection text to place candidate window as
4704         // adjacent to the selection text.
4705         //
4706         // in Qt5, cursor rectangle is used as QRectF internally, and it
4707         // will be checked by QRectF::isValid(), which will mark rectangle
4708         // with width == 0 or height == 0 as invalid.
4709         auto lineHeight = renderer()->lineHeight();
4710         return QRect(cursorToCoordinate(m_cursor, true, false), QSize(1, lineHeight ? lineHeight : 1));
4711     }
4712 
4713     case Qt::ImFont:
4714         return renderer()->currentFont();
4715 
4716     case Qt::ImCursorPosition:
4717         return m_cursor.column();
4718 
4719     case Qt::ImAnchorPosition:
4720         // If selectAnchor is at the same line, return the real anchor position
4721         // Otherwise return the same position of cursor
4722         if (view()->selection() && m_selectAnchor.line() == m_cursor.line()) {
4723             return m_selectAnchor.column();
4724         } else {
4725             return m_cursor.column();
4726         }
4727 
4728     case Qt::ImSurroundingText:
4729         return doc()->kateTextLine(m_cursor.line()).text();
4730 
4731     case Qt::ImCurrentSelection:
4732         if (view()->selection()) {
4733             return view()->selectionText();
4734         } else {
4735             return QString();
4736         }
4737     default:
4738         /* values: ImMaximumTextLength */
4739         break;
4740     }
4741 
4742     return QWidget::inputMethodQuery(query);
4743 }
4744 
4745 void KateViewInternal::inputMethodEvent(QInputMethodEvent *e)
4746 {
4747     if (doc()->readOnly()) {
4748         e->ignore();
4749         return;
4750     }
4751 
4752     // qCDebug(LOG_KTE) << "Event: cursor" << m_cursor << "commit" << e->commitString() << "preedit" << e->preeditString() << "replacement start" <<
4753     // e->replacementStart() << "length" << e->replacementLength();
4754 
4755     if (!m_imPreeditRange) {
4756         m_imPreeditRange.reset(
4757             doc()->newMovingRange(KTextEditor::Range(m_cursor, m_cursor), KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight));
4758     }
4759 
4760     if (!m_imPreeditRange->toRange().isEmpty()) {
4761         doc()->inputMethodStart();
4762         doc()->removeText(*m_imPreeditRange);
4763         doc()->inputMethodEnd();
4764     }
4765 
4766     if (!e->commitString().isEmpty() || e->replacementLength() || !e->preeditString().isEmpty()) {
4767         view()->removeSelectedText();
4768     }
4769 
4770     if (!e->commitString().isEmpty() || e->replacementLength()) {
4771         KTextEditor::Range preeditRange = *m_imPreeditRange;
4772 
4773         KTextEditor::Cursor start(m_imPreeditRange->start().line(), m_imPreeditRange->start().column() + e->replacementStart());
4774         KTextEditor::Cursor removeEnd = start + KTextEditor::Cursor(0, e->replacementLength());
4775 
4776         doc()->editStart();
4777         if (start != removeEnd) {
4778             doc()->removeText(KTextEditor::Range(start, removeEnd));
4779         }
4780 
4781         // if the input method event is text that should be inserted, call KTextEditor::DocumentPrivate::typeChars()
4782         // with the text. that method will handle the input and take care of overwrite mode, etc.
4783         // we call this via keyPressEvent, as that handles our input methods, see bug 357062
4784         QKeyEvent ke(QEvent::KeyPress, 0, Qt::NoModifier, e->commitString());
4785         keyPressEvent(&ke);
4786 
4787         doc()->editEnd();
4788 
4789         // Revert to the same range as above
4790         m_imPreeditRange->setRange(preeditRange);
4791     }
4792 
4793     if (!e->preeditString().isEmpty()) {
4794         doc()->inputMethodStart();
4795         doc()->insertText(m_imPreeditRange->start(), e->preeditString());
4796         doc()->inputMethodEnd();
4797         // The preedit range gets automatically repositioned
4798     }
4799 
4800     // Finished this input method context?
4801     if (m_imPreeditRange && e->preeditString().isEmpty()) {
4802         // delete the range and reset the pointer
4803         m_imPreeditRange.reset();
4804         m_imPreeditRangeChildren.clear();
4805 
4806         if (QApplication::cursorFlashTime() > 0) {
4807             renderer()->setDrawCaret(false);
4808         }
4809         renderer()->setCaretOverrideColor(QColor());
4810 
4811         e->accept();
4812         return;
4813     }
4814 
4815     KTextEditor::Cursor newCursor = m_cursor;
4816     bool hideCursor = false;
4817     QColor caretColor;
4818 
4819     if (m_imPreeditRange) {
4820         m_imPreeditRangeChildren.clear();
4821 
4822         int decorationColumn = 0;
4823         const auto &attributes = e->attributes();
4824         for (auto &a : attributes) {
4825             if (a.type == QInputMethodEvent::Cursor) {
4826                 const int cursor = qMin(a.start, e->preeditString().length());
4827                 newCursor = m_imPreeditRange->start() + KTextEditor::Cursor(0, cursor);
4828                 hideCursor = !a.length;
4829                 QColor c = qvariant_cast<QColor>(a.value);
4830                 if (c.isValid()) {
4831                     caretColor = c;
4832                 }
4833 
4834             } else if (a.type == QInputMethodEvent::TextFormat) {
4835                 QTextCharFormat f = qvariant_cast<QTextFormat>(a.value).toCharFormat();
4836                 const int start = qMin(a.start, e->preeditString().length());
4837                 const int end = qMin(a.start + a.length, e->preeditString().length());
4838                 if (f.isValid() && decorationColumn <= start && start != end) {
4839                     const KTextEditor::MovingCursor &preEditRangeStart = m_imPreeditRange->start();
4840                     const int startLine = preEditRangeStart.line();
4841                     const int startCol = preEditRangeStart.column();
4842                     KTextEditor::Range fr(startLine, startCol + start, startLine, startCol + end);
4843                     std::unique_ptr<KTextEditor::MovingRange> formatRange(doc()->newMovingRange(fr));
4844                     KTextEditor::Attribute::Ptr attribute(new KTextEditor::Attribute());
4845                     attribute->merge(f);
4846                     formatRange->setAttribute(attribute);
4847                     decorationColumn = end;
4848                     m_imPreeditRangeChildren.push_back(std::move(formatRange));
4849                 }
4850             }
4851         }
4852     }
4853 
4854     renderer()->setDrawCaret(hideCursor);
4855     renderer()->setCaretOverrideColor(caretColor);
4856 
4857     if (newCursor != m_cursor.toCursor()) {
4858         updateCursor(newCursor);
4859     }
4860 
4861     e->accept();
4862 }
4863 
4864 // END IM INPUT STUFF
4865 
4866 void KateViewInternal::flashChar(const KTextEditor::Cursor pos, KTextEditor::Attribute::Ptr attribute)
4867 {
4868     Q_ASSERT(pos.isValid());
4869     Q_ASSERT(attribute.constData());
4870 
4871     // if line is folded away, do nothing
4872     if (!view()->textFolding().isLineVisible(pos.line())) {
4873         return;
4874     }
4875 
4876     KTextEditor::Range range(pos, KTextEditor::Cursor(pos.line(), pos.column() + 1));
4877     if (m_textAnimation) {
4878         m_textAnimation->deleteLater();
4879     }
4880     m_textAnimation = new KateTextAnimation(range, std::move(attribute), this);
4881 }
4882 
4883 void KateViewInternal::showBracketMatchPreview()
4884 {
4885     // only show when main window is active
4886     if (window() && !window()->isActiveWindow()) {
4887         return;
4888     }
4889 
4890     const KTextEditor::Cursor openBracketCursor = m_bmStart->start();
4891     // make sure that the matching bracket is an opening bracket that is not visible on the current view, and that the preview won't be blocking the cursor
4892     if (m_cursor == openBracketCursor || toVirtualCursor(openBracketCursor).line() >= startLine() || m_cursor.line() - startLine() < 2) {
4893         hideBracketMatchPreview();
4894         return;
4895     }
4896 
4897     if (!m_bmPreview) {
4898         m_bmPreview.reset(new KateTextPreview(m_view, this));
4899         m_bmPreview->setAttribute(Qt::WA_ShowWithoutActivating);
4900         m_bmPreview->setFrameStyle(QFrame::Box);
4901     }
4902 
4903     const int previewLine = openBracketCursor.line();
4904     KateRenderer *const renderer_ = renderer();
4905     KateLineLayout *lineLayout(new KateLineLayout(*renderer_));
4906     lineLayout->setLine(previewLine, -1);
4907 
4908     // If the opening bracket is on its own line, start preview at the line above it instead (where the context is likely to be)
4909     const int col = lineLayout->textLine().firstChar();
4910     if (previewLine > 0 && (col == -1 || col == openBracketCursor.column())) {
4911         lineLayout->setLine(previewLine - 1, lineLayout->virtualLine() - 1);
4912     }
4913 
4914     renderer_->layoutLine(lineLayout, -1 /* no wrap */, false /* no layout cache */);
4915     const int lineWidth =
4916         qBound(m_view->width() / 5, int(lineLayout->width() + renderer_->spaceWidth() * 2), m_view->width() - m_leftBorder->width() - m_lineScroll->width());
4917     m_bmPreview->resize(lineWidth, renderer_->lineHeight() * 2);
4918     const QPoint topLeft = mapToGlobal(QPoint(0, 0));
4919     m_bmPreview->move(topLeft.x(), topLeft.y());
4920     m_bmPreview->setLine(lineLayout->virtualLine());
4921     m_bmPreview->setCenterView(false);
4922     m_bmPreview->raise();
4923     m_bmPreview->show();
4924 }
4925 
4926 void KateViewInternal::hideBracketMatchPreview()
4927 {
4928     m_bmPreview.reset();
4929 }
4930 
4931 void KateViewInternal::documentTextInserted(KTextEditor::Document *, KTextEditor::Range range)
4932 {
4933 #ifndef QT_NO_ACCESSIBILITY
4934     if (view()->m_accessibilityEnabled && QAccessible::isActive()) {
4935         auto doc = view()->doc();
4936         QAccessibleTextInsertEvent ev(this, doc->cursorToOffset(range.start()), doc->text(range));
4937         QAccessible::updateAccessibility(&ev);
4938     }
4939 #endif
4940 }
4941 
4942 void KateViewInternal::documentTextRemoved(KTextEditor::Document * /*document*/, KTextEditor::Range range, const QString &oldText)
4943 {
4944 #ifndef QT_NO_ACCESSIBILITY
4945     if (view()->m_accessibilityEnabled && QAccessible::isActive()) {
4946         auto doc = view()->doc();
4947         QAccessibleTextRemoveEvent ev(this, doc->cursorToOffset(range.start()), oldText);
4948         QAccessible::updateAccessibility(&ev);
4949     }
4950 #endif
4951 }
4952 
4953 QRect KateViewInternal::inlineNoteRect(const KateInlineNoteData &noteData) const
4954 {
4955     KTextEditor::InlineNote note(noteData);
4956     // compute note width and position
4957     const auto noteWidth = note.width();
4958     auto noteCursor = note.position();
4959 
4960     // The cursor might be outside of the text. In that case, clamp it to the text and
4961     // later on add the missing x offset.
4962     const auto lineLength = view()->document()->lineLength(noteCursor.line());
4963     int extraOffset = -noteWidth;
4964     if (noteCursor.column() == lineLength) {
4965         extraOffset = 0;
4966     } else if (noteCursor.column() > lineLength) {
4967         extraOffset = (noteCursor.column() - lineLength) * renderer()->spaceWidth();
4968         noteCursor.setColumn(lineLength);
4969     }
4970     auto noteStartPos = mapToGlobal(cursorToCoordinate(noteCursor, true, false));
4971     bool rtl = false;
4972     if (view()->dynWordWrap()) {
4973         const KateLineLayout *lineLayout = cache()->line(noteCursor.line());
4974         rtl = lineLayout && lineLayout->layout()->textOption().textDirection() == Qt::RightToLeft;
4975     }
4976     if (rtl) {
4977         noteStartPos.rx() -= note.width();
4978         extraOffset = -extraOffset;
4979     }
4980 
4981     // compute the note's rect
4982     auto globalNoteRect = QRect(noteStartPos + QPoint{extraOffset, 0}, QSize(noteWidth, renderer()->lineHeight()));
4983 
4984     return globalNoteRect;
4985 }
4986 
4987 KateInlineNoteData KateViewInternal::inlineNoteAt(const QPoint &globalPos) const
4988 {
4989     // compute the associated cursor to get the right line
4990     const int line = coordinatesToCursor(mapFromGlobal(globalPos)).line();
4991     const auto inlineNotes = view()->inlineNotes(line);
4992     // loop over all notes and check if the point is inside it
4993     for (const auto &note : inlineNotes) {
4994         auto globalNoteRect = inlineNoteRect(note);
4995         if (globalNoteRect.contains(globalPos)) {
4996             return note;
4997         }
4998     }
4999     // none found -- return an invalid note
5000     return {};
5001 }
5002 
5003 bool KateViewInternal::sendMouseEventToInputContext(QMouseEvent *e)
5004 {
5005     if (!m_imPreeditRange) {
5006         return false;
5007     }
5008 
5009     KTextEditor::Cursor c = cursorForPoint(e->pos());
5010     if (!m_imPreeditRange->contains(c) && c != m_imPreeditRange->end()) {
5011         return false;
5012     }
5013 
5014     auto cursorPos = (c - m_imPreeditRange->start());
5015 
5016     if (cursorPos.column() >= 0) {
5017         if (e->type() == QEvent::MouseButtonRelease)
5018             QGuiApplication::inputMethod()->invokeAction(QInputMethod::Click, cursorPos.column());
5019         e->setAccepted(true);
5020         return true;
5021     }
5022     return false;
5023 }
5024 
5025 void KateViewInternal::commitPreedit()
5026 {
5027     if (!m_imPreeditRange) {
5028         return;
5029     }
5030 
5031     QGuiApplication::inputMethod()->commit();
5032 }
5033 
5034 #include "moc_kateviewinternal.cpp"