File indexing completed on 2024-05-05 16:18:27

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