1 /*
2     This file is part of the KDE libraries
3     SPDX-FileCopyrightText: 2002 John Firebaugh <jfirebaugh@kde.org>
4     SPDX-FileCopyrightText: 2002 Joseph Wenninger <jowenn@kde.org>
5     SPDX-FileCopyrightText: 2002, 2003 Christoph Cullmann <cullmann@kde.org>
6     SPDX-FileCopyrightText: 2002-2007 Hamish Rodda <rodda@kde.org>
7     SPDX-FileCopyrightText: 2003 Anakim Border <aborder@sources.sourceforge.net>
8     SPDX-FileCopyrightText: 2007 Mirko Stocker <me@misto.ch>
9     SPDX-FileCopyrightText: 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
10     SPDX-FileCopyrightText: 2008 Erlend Hamberg <ehamberg@gmail.com>
11 
12     Based on KWriteView:
13     SPDX-FileCopyrightText: 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de>
14 
15     SPDX-License-Identifier: LGPL-2.0-only
16 */
17 #include "kateviewinternal.h"
18 
19 #include "kateabstractinputmode.h"
20 #include "kateabstractinputmodefactory.h"
21 #include "katebuffer.h"
22 #include "katecompletionwidget.h"
23 #include "kateconfig.h"
24 #include "kateglobal.h"
25 #include "katehighlight.h"
26 #include "katelayoutcache.h"
27 #include "katemessagewidget.h"
28 #include "katepartdebug.h"
29 #include "katetextanimation.h"
30 #include "katetextpreview.h"
31 #include "kateview.h"
32 #include "kateviewaccessible.h"
33 #include "kateviewhelpers.h"
34 #include "spellcheck/spellingmenu.h"
35 
36 #include <KCursor>
37 #include <ktexteditor/documentcursor.h>
38 #include <ktexteditor/inlinenoteprovider.h>
39 #include <ktexteditor/movingrange.h>
40 #include <ktexteditor/texthintinterface.h>
41 
42 #include <QAccessible>
43 #include <QApplication>
44 #include <QClipboard>
45 #include <QKeyEvent>
46 #include <QLayout>
47 #include <QMimeData>
48 #include <QPainter>
49 #include <QPixmap>
50 #include <QScroller>
51 #include <QStyle>
52 #include <QToolTip>
53 
54 static const bool debugPainting = false;
55 
56 class ZoomEventFilter
57 {
58 public:
59     ZoomEventFilter() = default;
60 
detectZoomingEvent(QWheelEvent * e,Qt::KeyboardModifiers modifier=Qt::ControlModifier)61     bool detectZoomingEvent(QWheelEvent *e, Qt::KeyboardModifiers modifier = Qt::ControlModifier)
62     {
63         Qt::KeyboardModifiers modState = e->modifiers();
64         if (modState == modifier) {
65             if (m_lastWheelEvent.isValid()) {
66                 const qint64 deltaT = m_lastWheelEvent.elapsed();
67                 // Pressing the specified modifier key within 200ms of the previous "unmodified"
68                 // wheelevent is not allowed to toggle on text zooming
69                 if (m_lastWheelEventUnmodified && deltaT < 200) {
70                     m_ignoreZoom = true;
71                 } else if (deltaT > 1000) {
72                     // the protection is kept active for 1s after the last wheel event
73                     // TODO: this value should be tuned, preferably by someone using
74                     // Ctrl+Wheel zooming frequently.
75                     m_ignoreZoom = false;
76                 }
77             } else {
78                 // we can't say anything and have to assume there's nothing
79                 // accidental to the modifier being pressed.
80                 m_ignoreZoom = false;
81             }
82             m_lastWheelEventUnmodified = false;
83             if (m_ignoreZoom) {
84                 // unset the modifier so the view scrollbars can handle the scroll
85                 // event and produce normal, not accelerated scrolling
86                 modState &= ~modifier;
87                 e->setModifiers(modState);
88             }
89         } else {
90             // state is reset after any wheel event without the zoom modifier
91             m_lastWheelEventUnmodified = true;
92             m_ignoreZoom = false;
93         }
94         m_lastWheelEvent.start();
95 
96         // inform the caller whether this event is allowed to trigger text zooming.
97         return !m_ignoreZoom && modState == modifier;
98     }
99 
100 protected:
101     QElapsedTimer m_lastWheelEvent;
102     bool m_ignoreZoom = false;
103     bool m_lastWheelEventUnmodified = false;
104 };
105 
KateViewInternal(KTextEditor::ViewPrivate * view)106 KateViewInternal::KateViewInternal(KTextEditor::ViewPrivate *view)
107     : QWidget(view)
108     , editSessionNumber(0)
109     , editIsRunning(false)
110     , m_view(view)
111     , m_cursor(doc()->buffer(), KTextEditor::Cursor(0, 0), Kate::TextCursor::MoveOnInsert)
112     , m_mouse()
113     , m_possibleTripleClick(false)
114     , m_completionItemExpanded(false)
115     , m_bm(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand))
116     , m_bmStart(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand))
117     , m_bmEnd(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand))
118     , m_bmLastFlashPos(doc()->newMovingCursor(KTextEditor::Cursor::invalid()))
119 
120     // folding marker
121     , m_fmStart(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand))
122     , m_fmEnd(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand))
123 
124     , m_dummy(nullptr)
125 
126     // stay on cursor will avoid that the view scroll around on press return at beginning
127     , m_startPos(doc()->buffer(), KTextEditor::Cursor(0, 0), Kate::TextCursor::StayOnInsert)
128 
129     , m_visibleLineCount(0)
130     , m_madeVisible(false)
131     , m_shiftKeyPressed(false)
132     , m_autoCenterLines(0)
133     , m_minLinesVisible(0)
134     , m_selChangedByUser(false)
135     , m_selectAnchor(-1, -1)
136     , m_selectionMode(Default)
137     , m_layoutCache(new KateLayoutCache(renderer(), this))
138     , m_preserveX(false)
139     , m_preservedX(0)
140     , m_cachedMaxStartPos(-1, -1)
141     , m_dragScrollTimer(this)
142     , m_scrollTimer(this)
143     , m_cursorTimer(this)
144     , m_textHintTimer(this)
145     , m_textHintDelay(500)
146     , m_textHintPos(-1, -1)
147     , m_imPreeditRange(nullptr)
148 {
149     // setup input modes
150     Q_ASSERT(m_inputModes.size() == KTextEditor::EditorPrivate::self()->inputModeFactories().size());
151     m_inputModes[KTextEditor::View::NormalInputMode].reset(
152         KTextEditor::EditorPrivate::self()->inputModeFactories()[KTextEditor::View::NormalInputMode]->createInputMode(this));
153     m_inputModes[KTextEditor::View::ViInputMode].reset(
154         KTextEditor::EditorPrivate::self()->inputModeFactories()[KTextEditor::View::ViInputMode]->createInputMode(this));
155     m_currentInputMode = m_inputModes[KTextEditor::View::NormalInputMode].get();
156 
157     setMinimumSize(0, 0);
158     setAttribute(Qt::WA_OpaquePaintEvent);
159     setAttribute(Qt::WA_InputMethodEnabled);
160 
161     // invalidate m_selectionCached.start(), or keyb selection is screwed initially
162     m_selectionCached = KTextEditor::Range::invalid();
163 
164     // bracket markers are only for this view and should not be printed
165     m_bm->setView(m_view);
166     m_bmStart->setView(m_view);
167     m_bmEnd->setView(m_view);
168     m_bm->setAttributeOnlyForViews(true);
169     m_bmStart->setAttributeOnlyForViews(true);
170     m_bmEnd->setAttributeOnlyForViews(true);
171 
172     // use z depth defined in moving ranges interface
173     m_bm->setZDepth(-1000.0);
174     m_bmStart->setZDepth(-1000.0);
175     m_bmEnd->setZDepth(-1000.0);
176 
177     // update mark attributes
178     updateBracketMarkAttributes();
179 
180     //
181     // scrollbar for lines
182     //
183     m_lineScroll = new KateScrollBar(Qt::Vertical, this);
184     m_lineScroll->show();
185     m_lineScroll->setTracking(true);
186     m_lineScroll->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
187 
188     // Hijack the line scroller's controls, so we can scroll nicely for word-wrap
189     connect(m_lineScroll, &KateScrollBar::actionTriggered, this, &KateViewInternal::scrollAction);
190 
191     auto viewScrollLinesSlot = qOverload<int>(&KateViewInternal::scrollLines);
192     connect(m_lineScroll, &KateScrollBar::sliderMoved, this, viewScrollLinesSlot);
193     connect(m_lineScroll, &KateScrollBar::sliderMMBMoved, this, viewScrollLinesSlot);
194     connect(m_lineScroll, &KateScrollBar::valueChanged, this, viewScrollLinesSlot);
195 
196     //
197     // scrollbar for columns
198     //
199     m_columnScroll = new QScrollBar(Qt::Horizontal, m_view);
200     m_scroller = QScroller::scroller(this);
201     QScrollerProperties prop;
202     prop.setScrollMetric(QScrollerProperties::DecelerationFactor, 0.3);
203     prop.setScrollMetric(QScrollerProperties::MaximumVelocity, 1);
204     prop.setScrollMetric(QScrollerProperties::AcceleratingFlickMaximumTime, 0.2); // Workaround for QTBUG-88249 (non-flick gestures recognized as accelerating flick)
205     prop.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff);
206     prop.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff);
207     prop.setScrollMetric(QScrollerProperties::DragStartDistance, 0.0);
208     m_scroller->setScrollerProperties(prop);
209     m_scroller->grabGesture(this);
210 
211     if (m_view->dynWordWrap()) {
212         m_columnScroll->hide();
213     } else {
214         m_columnScroll->show();
215     }
216 
217     m_columnScroll->setTracking(true);
218     m_startX = 0;
219 
220     connect(m_columnScroll, &QScrollBar::valueChanged, this, &KateViewInternal::scrollColumns);
221 
222     // bottom corner box
223     m_dummy = new QWidget(m_view);
224     m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height());
225     m_dummy->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
226 
227     if (m_view->dynWordWrap()) {
228         m_dummy->hide();
229     } else {
230         m_dummy->show();
231     }
232 
233     cache()->setWrap(m_view->dynWordWrap());
234 
235     //
236     // iconborder ;)
237     //
238     m_leftBorder = new KateIconBorder(this, m_view);
239     m_leftBorder->show();
240 
241     // update view if folding ranges change
242     connect(&m_view->textFolding(), &Kate::TextFolding::foldingRangesChanged, this, &KateViewInternal::slotRegionVisibilityChanged);
243 
244     m_displayCursor.setPosition(0, 0);
245 
246     setAcceptDrops(true);
247 
248     m_zoomEventFilter.reset(new ZoomEventFilter());
249     // event filter
250     installEventFilter(this);
251 
252     // set initial cursor
253     m_mouseCursor = Qt::IBeamCursor;
254     setCursor(m_mouseCursor);
255 
256     // call mouseMoveEvent also if no mouse button is pressed
257     setMouseTracking(true);
258 
259     m_dragInfo.state = diNone;
260 
261     // timers
262     connect(&m_dragScrollTimer, &QTimer::timeout, this, &KateViewInternal::doDragScroll);
263 
264     connect(&m_scrollTimer, &QTimer::timeout, this, &KateViewInternal::scrollTimeout);
265 
266     connect(&m_cursorTimer, &QTimer::timeout, this, &KateViewInternal::cursorTimeout);
267 
268     connect(&m_textHintTimer, &QTimer::timeout, this, &KateViewInternal::textHintTimeout);
269 
270     // selection changed to set anchor
271     connect(m_view, &KTextEditor::ViewPrivate::selectionChanged, this, &KateViewInternal::viewSelectionChanged);
272 
273 #ifndef QT_NO_ACCESSIBILITY
274     QAccessible::installFactory(accessibleInterfaceFactory);
275 #endif
276     connect(doc(), &KTextEditor::DocumentPrivate::textInsertedRange, this, &KateViewInternal::documentTextInserted);
277     connect(doc(), &KTextEditor::DocumentPrivate::textRemoved, this, &KateViewInternal::documentTextRemoved);
278 
279     // update is called in KTextEditor::ViewPrivate, after construction and layout is over
280     // but before any other kateviewinternal call
281 }
282 
~KateViewInternal()283 KateViewInternal::~KateViewInternal()
284 {
285     // delete text animation object here, otherwise it updates the view in its destructor
286     delete m_textAnimation;
287 
288 #ifndef QT_NO_ACCESSIBILITY
289     QAccessible::removeFactory(accessibleInterfaceFactory);
290 #endif
291 }
292 
prepareForDynWrapChange()293 void KateViewInternal::prepareForDynWrapChange()
294 {
295     // Which is the current view line?
296     m_wrapChangeViewLine = cache()->displayViewLine(m_displayCursor, true);
297 }
298 
dynWrapChanged()299 void KateViewInternal::dynWrapChanged()
300 {
301     m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height());
302     if (view()->dynWordWrap()) {
303         m_columnScroll->hide();
304         m_dummy->hide();
305 
306     } else {
307         // column scrollbar + bottom corner box
308         m_columnScroll->show();
309         m_dummy->show();
310     }
311 
312     cache()->setWrap(view()->dynWordWrap());
313     updateView();
314 
315     if (view()->dynWordWrap()) {
316         scrollColumns(0);
317     }
318 
319     // Determine where the cursor should be to get the cursor on the same view line
320     if (m_wrapChangeViewLine != -1) {
321         KTextEditor::Cursor newStart = viewLineOffset(m_displayCursor, -m_wrapChangeViewLine);
322         makeVisible(newStart, newStart.column(), true);
323 
324     } else {
325         update();
326     }
327 }
328 
endPos() const329 KTextEditor::Cursor KateViewInternal::endPos() const
330 {
331     // Hrm, no lines laid out at all??
332     if (!cache()->viewCacheLineCount()) {
333         return KTextEditor::Cursor();
334     }
335 
336     for (int i = qMin(linesDisplayed() - 1, cache()->viewCacheLineCount() - 1); i >= 0; i--) {
337         const KateTextLayout &thisLine = cache()->viewLine(i);
338 
339         if (thisLine.line() == -1) {
340             continue;
341         }
342 
343         if (thisLine.virtualLine() >= view()->textFolding().visibleLines()) {
344             // Cache is too out of date
345             return KTextEditor::Cursor(view()->textFolding().visibleLines() - 1,
346                                        doc()->lineLength(view()->textFolding().visibleLineToLine(view()->textFolding().visibleLines() - 1)));
347         }
348 
349         return KTextEditor::Cursor(thisLine.virtualLine(), thisLine.wrap() ? thisLine.endCol() - 1 : thisLine.endCol());
350     }
351 
352     // can happen, if view is still invisible
353     return KTextEditor::Cursor();
354 }
355 
endLine() const356 int KateViewInternal::endLine() const
357 {
358     return endPos().line();
359 }
360 
yToKateTextLayout(int y) const361 KateTextLayout KateViewInternal::yToKateTextLayout(int y) const
362 {
363     if (y < 0 || y > size().height()) {
364         return KateTextLayout::invalid();
365     }
366 
367     int range = y / renderer()->lineHeight();
368 
369     // lineRanges is always bigger than 0, after the initial updateView call
370     if (range >= 0 && range < cache()->viewCacheLineCount()) {
371         return cache()->viewLine(range);
372     }
373 
374     return KateTextLayout::invalid();
375 }
376 
lineToY(int viewLine) const377 int KateViewInternal::lineToY(int viewLine) const
378 {
379     return (viewLine - startLine()) * renderer()->lineHeight();
380 }
381 
slotIncFontSizes(qreal step)382 void KateViewInternal::slotIncFontSizes(qreal step)
383 {
384     renderer()->increaseFontSizes(step);
385 }
386 
slotDecFontSizes(qreal step)387 void KateViewInternal::slotDecFontSizes(qreal step)
388 {
389     renderer()->decreaseFontSizes(step);
390 }
391 
slotResetFontSizes()392 void KateViewInternal::slotResetFontSizes()
393 {
394     renderer()->resetFontSizes();
395 }
396 
397 /**
398  * Line is the real line number to scroll to.
399  */
scrollLines(int line)400 void KateViewInternal::scrollLines(int line)
401 {
402     KTextEditor::Cursor newPos(line, 0);
403     scrollPos(newPos);
404 }
405 
406 // This can scroll less than one true line
scrollViewLines(int offset)407 void KateViewInternal::scrollViewLines(int offset)
408 {
409     KTextEditor::Cursor c = viewLineOffset(startPos(), offset);
410     scrollPos(c);
411 
412     bool blocked = m_lineScroll->blockSignals(true);
413     m_lineScroll->setValue(startLine());
414     m_lineScroll->blockSignals(blocked);
415 }
416 
scrollAction(int action)417 void KateViewInternal::scrollAction(int action)
418 {
419     switch (action) {
420     case QAbstractSlider::SliderSingleStepAdd:
421         scrollNextLine();
422         break;
423 
424     case QAbstractSlider::SliderSingleStepSub:
425         scrollPrevLine();
426         break;
427 
428     case QAbstractSlider::SliderPageStepAdd:
429         scrollNextPage();
430         break;
431 
432     case QAbstractSlider::SliderPageStepSub:
433         scrollPrevPage();
434         break;
435 
436     case QAbstractSlider::SliderToMinimum:
437         top_home();
438         break;
439 
440     case QAbstractSlider::SliderToMaximum:
441         bottom_end();
442         break;
443     }
444 }
445 
scrollNextPage()446 void KateViewInternal::scrollNextPage()
447 {
448     scrollViewLines(qMax(linesDisplayed() - 1, 0));
449 }
450 
scrollPrevPage()451 void KateViewInternal::scrollPrevPage()
452 {
453     scrollViewLines(-qMax(linesDisplayed() - 1, 0));
454 }
455 
scrollPrevLine()456 void KateViewInternal::scrollPrevLine()
457 {
458     scrollViewLines(-1);
459 }
460 
scrollNextLine()461 void KateViewInternal::scrollNextLine()
462 {
463     scrollViewLines(1);
464 }
465 
maxStartPos(bool changed)466 KTextEditor::Cursor KateViewInternal::maxStartPos(bool changed)
467 {
468     cache()->setAcceptDirtyLayouts(true);
469 
470     if (m_cachedMaxStartPos.line() == -1 || changed) {
471         KTextEditor::Cursor end(view()->textFolding().visibleLines() - 1,
472                                 doc()->lineLength(view()->textFolding().visibleLineToLine(view()->textFolding().visibleLines() - 1)));
473 
474         if (view()->config()->scrollPastEnd()) {
475             m_cachedMaxStartPos = viewLineOffset(end, -m_minLinesVisible);
476         } else {
477             m_cachedMaxStartPos = viewLineOffset(end, -(linesDisplayed() - 1));
478         }
479     }
480 
481     cache()->setAcceptDirtyLayouts(false);
482 
483     return m_cachedMaxStartPos;
484 }
485 
486 // c is a virtual cursor
scrollPos(KTextEditor::Cursor & c,bool force,bool calledExternally,bool emitSignals)487 void KateViewInternal::scrollPos(KTextEditor::Cursor &c, bool force, bool calledExternally, bool emitSignals)
488 {
489     if (!force && ((!view()->dynWordWrap() && c.line() == startLine()) || c == startPos())) {
490         return;
491     }
492 
493     if (c.line() < 0) {
494         c.setLine(0);
495     }
496 
497     KTextEditor::Cursor limit = maxStartPos();
498     if (c > limit) {
499         c = limit;
500 
501         // Re-check we're not just scrolling to the same place
502         if (!force && ((!view()->dynWordWrap() && c.line() == startLine()) || c == startPos())) {
503             return;
504         }
505     }
506 
507     int viewLinesScrolled = 0;
508 
509     // only calculate if this is really used and useful, could be wrong here, please recheck
510     // for larger scrolls this makes 2-4 seconds difference on my xeon with dyn. word wrap on
511     // try to get it really working ;)
512     bool viewLinesScrolledUsable = !force && (c.line() >= startLine() - linesDisplayed() - 1) && (c.line() <= endLine() + linesDisplayed() + 1);
513 
514     if (viewLinesScrolledUsable) {
515         viewLinesScrolled = cache()->displayViewLine(c);
516     }
517 
518     m_startPos.setPosition(c);
519 
520     // set false here but reversed if we return to makeVisible
521     m_madeVisible = false;
522 
523     if (viewLinesScrolledUsable) {
524         int lines = linesDisplayed();
525         if (view()->textFolding().visibleLines() < lines) {
526             KTextEditor::Cursor end(view()->textFolding().visibleLines() - 1,
527                                     doc()->lineLength(view()->textFolding().visibleLineToLine(view()->textFolding().visibleLines() - 1)));
528             lines = qMin(linesDisplayed(), cache()->displayViewLine(end) + 1);
529         }
530 
531         Q_ASSERT(lines >= 0);
532 
533         if (!calledExternally && qAbs(viewLinesScrolled) < lines &&
534             // NOTE: on some machines we must update if the floating widget is visible
535             //       otherwise strange painting bugs may occur during scrolling...
536             !((view()->m_messageWidgets[KTextEditor::Message::TopInView] && view()->m_messageWidgets[KTextEditor::Message::TopInView]->isVisible())
537               || (view()->m_messageWidgets[KTextEditor::Message::CenterInView] && view()->m_messageWidgets[KTextEditor::Message::CenterInView]->isVisible())
538               || (view()->m_messageWidgets[KTextEditor::Message::BottomInView] && view()->m_messageWidgets[KTextEditor::Message::BottomInView]->isVisible()))) {
539             updateView(false, viewLinesScrolled);
540 
541             int scrollHeight = -(viewLinesScrolled * (int)renderer()->lineHeight());
542 
543             // scroll excluding child widgets (floating notifications)
544             scroll(0, scrollHeight, rect());
545             m_leftBorder->scroll(0, scrollHeight);
546 
547             if (emitSignals) {
548                 Q_EMIT view()->verticalScrollPositionChanged(m_view, c);
549                 Q_EMIT view()->displayRangeChanged(m_view);
550             }
551             return;
552         }
553     }
554 
555     updateView();
556     update();
557     m_leftBorder->update();
558     if (emitSignals) {
559         Q_EMIT view()->verticalScrollPositionChanged(m_view, c);
560         Q_EMIT view()->displayRangeChanged(m_view);
561     }
562 }
563 
scrollColumns(int x)564 void KateViewInternal::scrollColumns(int x)
565 {
566     if (x < 0) {
567         x = 0;
568     }
569 
570     if (x > m_columnScroll->maximum()) {
571         x = m_columnScroll->maximum();
572     }
573 
574     if (x == startX()) {
575         return;
576     }
577 
578     int dx = startX() - x;
579     m_startX = x;
580 
581     if (qAbs(dx) < width()) {
582         // scroll excluding child widgets (floating notifications)
583         scroll(dx, 0, rect());
584     } else {
585         update();
586     }
587 
588     Q_EMIT view()->horizontalScrollPositionChanged(m_view);
589     Q_EMIT view()->displayRangeChanged(m_view);
590 
591     bool blocked = m_columnScroll->blockSignals(true);
592     m_columnScroll->setValue(startX());
593     m_columnScroll->blockSignals(blocked);
594 }
595 
596 // If changed is true, the lines that have been set dirty have been updated.
updateView(bool changed,int viewLinesScrolled)597 void KateViewInternal::updateView(bool changed, int viewLinesScrolled)
598 {
599     if (!isVisible() && !viewLinesScrolled && !changed) {
600         return; // When this view is not visible, don't do anything
601     }
602 
603     view()->doc()->delayAutoReload(); // Don't reload while user scrolls around
604     bool blocked = m_lineScroll->blockSignals(true);
605 
606     int wrapWidth = width();
607     if (view()->config()->dynWrapAtStaticMarker() && view()->config()->dynWordWrap()) {
608         // We need to transform char count to a pixel width, stolen from PrintPainter::updateCache()
609         QString s;
610         s.fill(QLatin1Char('5'), view()->doc()->config()->wordWrapAt());
611         wrapWidth = qMin(width(), static_cast<int>(renderer()->currentFontMetrics().boundingRect(s).width()));
612     }
613 
614     if (wrapWidth != cache()->viewWidth()) {
615         cache()->setViewWidth(wrapWidth);
616         changed = true;
617     }
618 
619     /* It was observed that height() could be negative here --
620        when the main Kate view has 0 as size (during creation),
621        and there frame around KateViewInternal.  In which
622        case we'd set the view cache to 0 (or less!) lines, and
623        start allocating huge chunks of data, later. */
624     int newSize = (qMax(0, height()) / renderer()->lineHeight()) + 1;
625     cache()->updateViewCache(startPos(), newSize, viewLinesScrolled);
626     m_visibleLineCount = newSize;
627 
628     KTextEditor::Cursor maxStart = maxStartPos(changed);
629     int maxLineScrollRange = maxStart.line();
630     if (view()->dynWordWrap() && maxStart.column() != 0) {
631         maxLineScrollRange++;
632     }
633     m_lineScroll->setRange(0, maxLineScrollRange);
634 
635     m_lineScroll->setValue(startLine());
636     m_lineScroll->setSingleStep(1);
637     m_lineScroll->setPageStep(qMax(0, height()) / renderer()->lineHeight());
638     m_lineScroll->blockSignals(blocked);
639 
640     KateViewConfig::ScrollbarMode show_scrollbars = static_cast<KateViewConfig::ScrollbarMode>(view()->config()->showScrollbars());
641 
642     bool visible = ((show_scrollbars == KateViewConfig::AlwaysOn) || ((show_scrollbars == KateViewConfig::ShowWhenNeeded) && (maxLineScrollRange != 0)));
643     bool visible_dummy = visible;
644 
645     m_lineScroll->setVisible(visible);
646 
647     if (!view()->dynWordWrap()) {
648         int max = maxLen(startLine()) - width();
649         if (max < 0) {
650             max = 0;
651         }
652 
653         // if we lose the ability to scroll horizontally, move view to the far-left
654         if (max == 0) {
655             scrollColumns(0);
656         }
657 
658         blocked = m_columnScroll->blockSignals(true);
659 
660         // disable scrollbar
661         m_columnScroll->setDisabled(max == 0);
662 
663         visible = ((show_scrollbars == KateViewConfig::AlwaysOn) || ((show_scrollbars == KateViewConfig::ShowWhenNeeded) && (max != 0)));
664         visible_dummy &= visible;
665         m_columnScroll->setVisible(visible);
666 
667         m_columnScroll->setRange(0, max + (renderer()->spaceWidth() / 2)); // Add some space for the caret at EOL
668 
669         m_columnScroll->setValue(startX());
670 
671         // Approximate linescroll
672         m_columnScroll->setSingleStep(renderer()->currentFontMetrics().horizontalAdvance(QLatin1Char('a')));
673         m_columnScroll->setPageStep(width());
674 
675         m_columnScroll->blockSignals(blocked);
676     } else {
677         visible_dummy = false;
678     }
679 
680     m_dummy->setVisible(visible_dummy);
681 
682     if (changed) {
683         updateDirty();
684     }
685 }
686 
687 /**
688  * this function ensures a certain location is visible on the screen.
689  * if endCol is -1, ignore making the columns visible.
690  */
makeVisible(const KTextEditor::Cursor c,int endCol,bool force,bool center,bool calledExternally)691 void KateViewInternal::makeVisible(const KTextEditor::Cursor c, int endCol, bool force, bool center, bool calledExternally)
692 {
693     // qCDebug(LOG_KTE) << "MakeVisible start " << startPos() << " end " << endPos() << " -> request: " << c;// , new start [" << scroll.line << "," <<
694     // scroll.col << "] lines " << (linesDisplayed() - 1) << " height " << height(); if the line is in a folded region, unfold all the way up if (
695     // doc()->foldingTree()->findNodeForLine( c.line )->visible )
696     //  qCDebug(LOG_KTE)<<"line ("<<c.line<<") should be visible";
697 
698     const int lnDisp = linesDisplayed();
699     const int viewLine = cache()->displayViewLine(c, true);
700     const bool curBelowScreen = (viewLine == -2);
701 
702     if (force) {
703         KTextEditor::Cursor scroll = c;
704         scrollPos(scroll, force, calledExternally);
705     } else if (center && (c < startPos() || c > endPos())) {
706         KTextEditor::Cursor scroll = viewLineOffset(c, -int(lnDisp) / 2);
707         scrollPos(scroll, false, calledExternally);
708     } else if ((viewLine >= (lnDisp - m_minLinesVisible)) || (curBelowScreen)) {
709         KTextEditor::Cursor scroll = viewLineOffset(c, -(lnDisp - m_minLinesVisible - 1));
710         scrollPos(scroll, false, calledExternally);
711     } else if (c < viewLineOffset(startPos(), m_minLinesVisible)) {
712         KTextEditor::Cursor scroll = viewLineOffset(c, -m_minLinesVisible);
713         scrollPos(scroll, false, calledExternally);
714     } else {
715         // Check to see that we're not showing blank lines
716         KTextEditor::Cursor max = maxStartPos();
717         if (startPos() > max) {
718             scrollPos(max, max.column(), calledExternally);
719         }
720     }
721 
722     if (!view()->dynWordWrap() && (endCol != -1 || view()->wrapCursor())) {
723         KTextEditor::Cursor rc = toRealCursor(c);
724         int sX = renderer()->cursorToX(cache()->textLayout(rc), rc, !view()->wrapCursor());
725 
726         int sXborder = sX - 8;
727         if (sXborder < 0) {
728             sXborder = 0;
729         }
730 
731         if (sX < startX()) {
732             scrollColumns(sXborder);
733         } else if (sX > startX() + width()) {
734             scrollColumns(sX - width() + 8);
735         }
736     }
737 
738     m_madeVisible = !force;
739 
740 #ifndef QT_NO_ACCESSIBILITY
741     // FIXME -- is this needed?
742 //    QAccessible::updateAccessibility(this, KateCursorAccessible::ChildId, QAccessible::Focus);
743 #endif
744 }
745 
slotRegionVisibilityChanged()746 void KateViewInternal::slotRegionVisibilityChanged()
747 {
748     qCDebug(LOG_KTE);
749 
750     // ensure the layout cache is ok for the updateCursor calls below
751     // without the updateView() the view will jump to the bottom on hiding blocks after
752     // change cfb0af25bdfac0d8f86b42db0b34a6bc9f9a361e
753     cache()->clear();
754     updateView();
755 
756     m_cachedMaxStartPos.setLine(-1);
757     KTextEditor::Cursor max = maxStartPos();
758     if (startPos() > max) {
759         scrollPos(max, false, false, false /* don't emit signals! */);
760     }
761 
762     // if text was folded: make sure the cursor is on a visible line
763     qint64 foldedRangeId = -1;
764     if (!view()->textFolding().isLineVisible(m_cursor.line(), &foldedRangeId)) {
765         KTextEditor::Range foldingRange = view()->textFolding().foldingRange(foldedRangeId);
766         Q_ASSERT(foldingRange.start().isValid());
767 
768         // set cursor to start of folding region
769         updateCursor(foldingRange.start(), true);
770     } else {
771         // force an update of the cursor, since otherwise the m_displayCursor
772         // line may be below the total amount of visible lines.
773         updateCursor(m_cursor, true);
774     }
775 
776     updateView();
777     update();
778     m_leftBorder->update();
779 
780     // emit signals here, scrollPos has this disabled, to ensure we do this after all stuff is updated!
781     Q_EMIT view()->verticalScrollPositionChanged(m_view, max);
782     Q_EMIT view()->displayRangeChanged(m_view);
783 }
784 
slotRegionBeginEndAddedRemoved(unsigned int)785 void KateViewInternal::slotRegionBeginEndAddedRemoved(unsigned int)
786 {
787     qCDebug(LOG_KTE);
788     // FIXME: performance problem
789     m_leftBorder->update();
790 }
791 
showEvent(QShowEvent * e)792 void KateViewInternal::showEvent(QShowEvent *e)
793 {
794     updateView();
795 
796     QWidget::showEvent(e);
797 }
798 
attributeAt(const KTextEditor::Cursor position) const799 KTextEditor::Attribute::Ptr KateViewInternal::attributeAt(const KTextEditor::Cursor position) const
800 {
801     KTextEditor::Attribute::Ptr attrib(new KTextEditor::Attribute());
802 
803     Kate::TextLine kateLine = doc()->kateTextLine(position.line());
804     if (!kateLine) {
805         return attrib;
806     }
807 
808     *attrib = *m_view->renderer()->attribute(kateLine->attribute(position.column()));
809 
810     return attrib;
811 }
812 
linesDisplayed() const813 int KateViewInternal::linesDisplayed() const
814 {
815     int h = height();
816 
817     // catch zero heights, even if should not happen
818     int fh = qMax(1, renderer()->lineHeight());
819 
820     // default to 1, there is always one line around....
821     // too many places calc with linesDisplayed() - 1
822     return qMax(1, (h - (h % fh)) / fh);
823 }
824 
cursorToCoordinate(const KTextEditor::Cursor cursor,bool realCursor,bool includeBorder) const825 QPoint KateViewInternal::cursorToCoordinate(const KTextEditor::Cursor cursor, bool realCursor, bool includeBorder) const
826 {
827     if (cursor.line() >= doc()->lines()) {
828         return QPoint(-1, -1);
829     }
830 
831     int viewLine = cache()->displayViewLine(realCursor ? toVirtualCursor(cursor) : cursor, true);
832 
833     if (viewLine < 0 || viewLine >= cache()->viewCacheLineCount()) {
834         return QPoint(-1, -1);
835     }
836 
837     const int y = (int)viewLine * renderer()->lineHeight();
838 
839     KateTextLayout layout = cache()->viewLine(viewLine);
840 
841     if (cursor.column() > doc()->lineLength(cursor.line())) {
842         return QPoint(-1, -1);
843     }
844 
845     int x = 0;
846 
847     // only set x value if we have a valid layout (bug #171027)
848     if (layout.isValid()) {
849         x = (int)layout.lineLayout().cursorToX(cursor.column());
850     }
851     //  else
852     //    qCDebug(LOG_KTE) << "Invalid Layout";
853 
854     if (includeBorder) {
855         x += m_leftBorder->width();
856     }
857 
858     x -= startX();
859 
860     return QPoint(x, y);
861 }
862 
cursorCoordinates(bool includeBorder) const863 QPoint KateViewInternal::cursorCoordinates(bool includeBorder) const
864 {
865     return cursorToCoordinate(m_displayCursor, false, includeBorder);
866 }
867 
findMatchingBracket()868 KTextEditor::Cursor KateViewInternal::findMatchingBracket()
869 {
870     KTextEditor::Cursor c;
871 
872     if (!m_bm->toRange().isValid()) {
873         return KTextEditor::Cursor::invalid();
874     }
875 
876     Q_ASSERT(m_bmEnd->toRange().isValid());
877     Q_ASSERT(m_bmStart->toRange().isValid());
878 
879     // For e.g. the text "{|}" (where | is the cursor), m_bmStart is equal to [ (0, 0)  ->  (0, 1) ]
880     // and the closing bracket is in (0, 1). Thus, we check m_bmEnd first.
881     if (m_bmEnd->toRange().contains(m_cursor) || m_bmEnd->end() == m_cursor.toCursor()) {
882         c = m_bmStart->start();
883     } else if (m_bmStart->toRange().contains(m_cursor) || m_bmStart->end() == m_cursor.toCursor()) {
884         c = m_bmEnd->end();
885         // We need to adjust the cursor position in case of override mode, BUG-402594
886         if (doc()->config()->ovr()) {
887             c.setColumn(c.column() - 1);
888         }
889     } else {
890         // should never happen: a range exists, but the cursor position is
891         // neither at the start nor at the end...
892         return KTextEditor::Cursor::invalid();
893     }
894 
895     return c;
896 }
897 
898 class CalculatingCursor
899 {
900 public:
901     // These constructors constrain their arguments to valid positions
902     // before only the third one did, but that leads to crashes
903     // see bug 227449
CalculatingCursor(KateViewInternal * vi)904     CalculatingCursor(KateViewInternal *vi)
905         : m_vi(vi)
906     {
907         makeValid();
908     }
909 
CalculatingCursor(KateViewInternal * vi,const KTextEditor::Cursor c)910     CalculatingCursor(KateViewInternal *vi, const KTextEditor::Cursor c)
911         : m_cursor(c)
912         , m_vi(vi)
913     {
914         makeValid();
915     }
916 
CalculatingCursor(KateViewInternal * vi,int line,int col)917     CalculatingCursor(KateViewInternal *vi, int line, int col)
918         : m_cursor(line, col)
919         , m_vi(vi)
920     {
921         makeValid();
922     }
923 
~CalculatingCursor()924     virtual ~CalculatingCursor()
925     {
926     }
927 
line() const928     int line() const
929     {
930         return m_cursor.line();
931     }
932 
column() const933     int column() const
934     {
935         return m_cursor.column();
936     }
937 
operator KTextEditor::Cursor() const938     operator KTextEditor::Cursor() const
939     {
940         return m_cursor;
941     }
942 
943     virtual CalculatingCursor &operator+=(int n) = 0;
944 
945     virtual CalculatingCursor &operator-=(int n) = 0;
946 
operator ++()947     CalculatingCursor &operator++()
948     {
949         return operator+=(1);
950     }
951 
operator --()952     CalculatingCursor &operator--()
953     {
954         return operator-=(1);
955     }
956 
makeValid()957     void makeValid()
958     {
959         m_cursor.setLine(qBound(0, line(), int(doc()->lines() - 1)));
960         if (view()->wrapCursor()) {
961             m_cursor.setColumn(qBound(0, column(), doc()->lineLength(line())));
962         } else {
963             m_cursor.setColumn(qMax(0, column()));
964         }
965         Q_ASSERT(valid());
966     }
967 
toEdge(KateViewInternal::Bias bias)968     void toEdge(KateViewInternal::Bias bias)
969     {
970         if (bias == KateViewInternal::left) {
971             m_cursor.setColumn(0);
972         } else if (bias == KateViewInternal::right) {
973             m_cursor.setColumn(doc()->lineLength(line()));
974         }
975     }
976 
atEdge() const977     bool atEdge() const
978     {
979         return atEdge(KateViewInternal::left) || atEdge(KateViewInternal::right);
980     }
981 
atEdge(KateViewInternal::Bias bias) const982     bool atEdge(KateViewInternal::Bias bias) const
983     {
984         switch (bias) {
985         case KateViewInternal::left:
986             return column() == 0;
987         case KateViewInternal::none:
988             return atEdge();
989         case KateViewInternal::right:
990             return column() >= doc()->lineLength(line());
991         default:
992             Q_ASSERT(false);
993             return false;
994         }
995     }
996 
997 protected:
valid() const998     bool valid() const
999     {
1000         return line() >= 0 && line() < doc()->lines() && column() >= 0 && (!view()->wrapCursor() || column() <= doc()->lineLength(line()));
1001     }
view()1002     KTextEditor::ViewPrivate *view()
1003     {
1004         return m_vi->m_view;
1005     }
view() const1006     const KTextEditor::ViewPrivate *view() const
1007     {
1008         return m_vi->m_view;
1009     }
doc()1010     KTextEditor::DocumentPrivate *doc()
1011     {
1012         return view()->doc();
1013     }
doc() const1014     const KTextEditor::DocumentPrivate *doc() const
1015     {
1016         return view()->doc();
1017     }
1018     KTextEditor::Cursor m_cursor;
1019     KateViewInternal *m_vi;
1020 };
1021 
1022 class BoundedCursor : public CalculatingCursor
1023 {
1024 public:
BoundedCursor(KateViewInternal * vi)1025     BoundedCursor(KateViewInternal *vi)
1026         : CalculatingCursor(vi)
1027     {
1028     }
BoundedCursor(KateViewInternal * vi,const KTextEditor::Cursor c)1029     BoundedCursor(KateViewInternal *vi, const KTextEditor::Cursor c)
1030         : CalculatingCursor(vi, c)
1031     {
1032     }
BoundedCursor(KateViewInternal * vi,int line,int col)1033     BoundedCursor(KateViewInternal *vi, int line, int col)
1034         : CalculatingCursor(vi, line, col)
1035     {
1036     }
operator +=(int n)1037     CalculatingCursor &operator+=(int n) override
1038     {
1039         KateLineLayoutPtr thisLine = m_vi->cache()->line(line());
1040         if (!thisLine->isValid()) {
1041             qCWarning(LOG_KTE) << "Did not retrieve valid layout for line " << line();
1042             return *this;
1043         }
1044 
1045         const bool wrapCursor = view()->wrapCursor();
1046         int maxColumn = -1;
1047         if (n >= 0) {
1048             for (int i = 0; i < n; i++) {
1049                 if (column() >= thisLine->length()) {
1050                     if (wrapCursor) {
1051                         break;
1052 
1053                     } else if (view()->dynWordWrap()) {
1054                         // Don't go past the edge of the screen in dynamic wrapping mode
1055                         if (maxColumn == -1) {
1056                             maxColumn = thisLine->length() + ((m_vi->width() - thisLine->widthOfLastLine()) / m_vi->renderer()->spaceWidth()) - 1;
1057                         }
1058 
1059                         if (column() >= maxColumn) {
1060                             m_cursor.setColumn(maxColumn);
1061                             break;
1062                         }
1063 
1064                         m_cursor.setColumn(column() + 1);
1065 
1066                     } else {
1067                         m_cursor.setColumn(column() + 1);
1068                     }
1069 
1070                 } else {
1071                     m_cursor.setColumn(thisLine->layout()->nextCursorPosition(column()));
1072                 }
1073             }
1074         } else {
1075             for (int i = 0; i > n; i--) {
1076                 if (column() >= thisLine->length()) {
1077                     m_cursor.setColumn(column() - 1);
1078                 } else if (column() == 0) {
1079                     break;
1080                 } else {
1081                     m_cursor.setColumn(thisLine->layout()->previousCursorPosition(column()));
1082                 }
1083             }
1084         }
1085 
1086         Q_ASSERT(valid());
1087         return *this;
1088     }
operator -=(int n)1089     CalculatingCursor &operator-=(int n) override
1090     {
1091         return operator+=(-n);
1092     }
1093 };
1094 
1095 class WrappingCursor : public CalculatingCursor
1096 {
1097 public:
WrappingCursor(KateViewInternal * vi)1098     WrappingCursor(KateViewInternal *vi)
1099         : CalculatingCursor(vi)
1100     {
1101     }
WrappingCursor(KateViewInternal * vi,const KTextEditor::Cursor c)1102     WrappingCursor(KateViewInternal *vi, const KTextEditor::Cursor c)
1103         : CalculatingCursor(vi, c)
1104     {
1105     }
WrappingCursor(KateViewInternal * vi,int line,int col)1106     WrappingCursor(KateViewInternal *vi, int line, int col)
1107         : CalculatingCursor(vi, line, col)
1108     {
1109     }
1110 
operator +=(int n)1111     CalculatingCursor &operator+=(int n) override
1112     {
1113         KateLineLayoutPtr thisLine = m_vi->cache()->line(line());
1114         if (!thisLine->isValid()) {
1115             qCWarning(LOG_KTE) << "Did not retrieve a valid layout for line " << line();
1116             return *this;
1117         }
1118 
1119         if (n >= 0) {
1120             for (int i = 0; i < n; i++) {
1121                 if (column() >= thisLine->length()) {
1122                     // Have come to the end of a line
1123                     if (line() >= doc()->lines() - 1)
1124                     // Have come to the end of the document
1125                     {
1126                         break;
1127                     }
1128 
1129                     // Advance to the beginning of the next line
1130                     m_cursor.setColumn(0);
1131                     m_cursor.setLine(line() + 1);
1132 
1133                     // Retrieve the next text range
1134                     thisLine = m_vi->cache()->line(line());
1135                     if (!thisLine->isValid()) {
1136                         qCWarning(LOG_KTE) << "Did not retrieve a valid layout for line " << line();
1137                         return *this;
1138                     }
1139 
1140                     continue;
1141                 }
1142 
1143                 m_cursor.setColumn(thisLine->layout()->nextCursorPosition(column()));
1144             }
1145 
1146         } else {
1147             for (int i = 0; i > n; i--) {
1148                 if (column() == 0) {
1149                     // Have come to the start of the document
1150                     if (line() == 0) {
1151                         break;
1152                     }
1153 
1154                     // Start going back to the end of the last line
1155                     m_cursor.setLine(line() - 1);
1156 
1157                     // Retrieve the next text range
1158                     thisLine = m_vi->cache()->line(line());
1159                     if (!thisLine->isValid()) {
1160                         qCWarning(LOG_KTE) << "Did not retrieve a valid layout for line " << line();
1161                         return *this;
1162                     }
1163 
1164                     // Finish going back to the end of the last line
1165                     m_cursor.setColumn(thisLine->length());
1166 
1167                     continue;
1168                 }
1169 
1170                 if (column() > thisLine->length()) {
1171                     m_cursor.setColumn(column() - 1);
1172                 } else {
1173                     m_cursor.setColumn(thisLine->layout()->previousCursorPosition(column()));
1174                 }
1175             }
1176         }
1177 
1178         Q_ASSERT(valid());
1179         return *this;
1180     }
operator -=(int n)1181     CalculatingCursor &operator-=(int n) override
1182     {
1183         return operator+=(-n);
1184     }
1185 };
1186 
1187 /**
1188  * @brief The CamelCursor class
1189  *
1190  * This class allows for "camel humps" when moving the cursor
1191  * using Ctrl + Left / Right. Similarly, this will also get triggered
1192  * when you press Ctrl+Shift+Left/Right for selection and Ctrl+Del
1193  * Ctrl + backspace for deletion.
1194  *
1195  * It is absoloutely essential that if you move through a word in 'n'
1196  * jumps, you should be able to move back with exactly same 'n' movements
1197  * which you made when moving forward. Example:
1198  *
1199  * Word: KateViewInternal
1200  *
1201  * Moving cursor towards right while holding control will result in cursor
1202  * landing in the following places (assuming you start from column 0)
1203  *
1204  *     |   |       |
1205  * KateViewInternal
1206  *
1207  * Moving the cursor back to get to the starting position should also
1208  * take exactly 3 movements:
1209  *
1210  * |   |   |
1211  * KateViewInternal
1212  *
1213  * In addition to simple camel case, this class also handles snake_case
1214  * capitalized snake, and mixtures of Camel + snake/underscore, for example
1215  * m_someMember. If a word has underscores in it, for example:
1216  *
1217  * snake_case_word
1218  *
1219  * the leading underscore is considered part of the word and thus a cursor
1220  * movement will land right after the underscore. Moving the cursor to end
1221  * for the above word should be like this:
1222  *
1223  * startpos: 0
1224  *       |    |   |
1225  * snake_case_word
1226  *
1227  * When jumping back to startpos, exact same "spots" need to be hit on your way
1228  * back.
1229  *
1230  * If a word has multiple leading underscores: snake___case, the underscores will
1231  * be considered part of the word and thus a jump wil land us "after" the last underscore.
1232  *
1233  * There are some other variations in this, for example Capitalized words, or identifiers
1234  * with numbers in betweens. For such cases, All capitalized words are skipped in one go
1235  * till we are on some "non-capitalized" word. In this context, a number is a non-capitalized
1236  * word so will break the jump. Examples:
1237  *
1238  *   | |
1239  * W1RD
1240  *
1241  * but for WORD_CAPITAL, following will happen:
1242  *
1243  *      |      |
1244  * WORD_CAPITAL
1245  *
1246  * The first case here is tricky to handle for reverse movement. I haven't noticed any
1247  * cases which the current implementation is unable to handle but there might be some.
1248  *
1249  * With languages like PHP where you have $ as part of the identifier, the cursor jump
1250  * will break "after" dollar. Consider: $phpVar, Following will happen:
1251  *
1252  *  |  |  |
1253  * $phpVar
1254  *
1255  * And of course, the reverse will be exact opposite.
1256  *
1257  * Similar to PHP, with CSS Colors, jump will break on '#' charachter
1258  *
1259  * Please see the test cases testWordMovementSingleRow() for more examples/data.
1260  *
1261  * It is absoloutely essential to know that this class *only* gets triggered for
1262  * cursor movement if we are in a word.
1263  *
1264  * Note to bugfixer: If some bug occurs, before changing anything please add a test
1265  * case for the bug and make sure everything passes before and after. The test case
1266  * for this can be found autotests/src/camelcursortest.cpp.
1267  *
1268  * @author Waqar Ahmed <waqar.17a@gmail.com>
1269  */
1270 class CamelCursor : public CalculatingCursor
1271 {
1272 public:
CamelCursor(KateViewInternal * vi,const KTextEditor::Cursor c)1273     CamelCursor(KateViewInternal *vi, const KTextEditor::Cursor c)
1274         : CalculatingCursor(vi, c)
1275     {
1276     }
1277 
operator +=(int n)1278     CalculatingCursor &operator+=(int n) override
1279     {
1280         KateLineLayoutPtr thisLine = m_vi->cache()->line(line());
1281         if (!thisLine->isValid()) {
1282             qCWarning(LOG_KTE) << "Did not retrieve valid layout for line " << line();
1283             return *this;
1284         }
1285 
1286         if (n >= 0) {
1287             auto skipCaps = [](QStringView text, int &col) {
1288                 int count = 0;
1289                 while (col < text.size() && text.at(col).isUpper()) {
1290                     ++count;
1291                     ++col;
1292                 }
1293                 // if this is a letter, then it means we are in the
1294                 // middle of a word, step back one position so that
1295                 // we are at the last Cap letter
1296                 // Otherwise, it's an all cap word
1297                 if (count > 1 && col < text.size() && text.at(col).isLetterOrNumber()) {
1298                     --col;
1299                 }
1300             };
1301 
1302             int jump = -1;
1303             int col = column();
1304             const QString &text = thisLine->textLine()->text();
1305 
1306             if (col < text.size() && text.at(col).isUpper()) {
1307                 skipCaps(text, col);
1308             }
1309 
1310             for (int i = col; i < thisLine->length(); ++i) {
1311                 if (text.at(i).isUpper() || !text.at(i).isLetterOrNumber()) {
1312                     break;
1313                 }
1314                 ++col;
1315             }
1316 
1317             // eat any '_' that are after the word BEFORE any space happens
1318             if (col < text.size() && text.at(col) == QLatin1Char('_')) {
1319                 while (col < text.size() && text.at(col) == QLatin1Char('_')) {
1320                     ++col;
1321                 }
1322             }
1323 
1324             // Underscores eaten, so now eat any spaces till next word
1325             if (col < text.size() && text.at(col).isSpace()) {
1326                 while (col < text.size() && text.at(col).isSpace()) {
1327                     ++col;
1328                 }
1329             }
1330 
1331             jump = col < 0 || (column() == col) ? (column() + 1) : col;
1332             m_cursor.setColumn(jump);
1333         } else {
1334             int jump = -1;
1335 
1336             auto skipCapsRev = [](QStringView text, int &col) {
1337                 int count = 0;
1338                 while (col > 0 && text.at(col).isUpper()) {
1339                     ++count;
1340                     --col;
1341                 }
1342 
1343                 // if more than one cap found, and current
1344                 // column is not upper, we want to move ahead
1345                 // to the upper
1346                 if (count >= 1 && col >= 0 && !text.at(col).isUpper()) {
1347                     ++col;
1348                 }
1349             };
1350 
1351             const QString &text = thisLine->textLine()->text();
1352             int col = std::min(column(), text.size() - 1);
1353             col = col - 1;
1354 
1355             // skip any spaces
1356             if (col > 0 && text.at(col).isSpace()) {
1357                 while (text.at(col).isSpace() && col > 0) {
1358                     --col;
1359                 }
1360             }
1361 
1362             // Skip Underscores
1363             if (col > 0 && text.at(col) == QLatin1Char('_')) {
1364                 while (col > 0 && text.at(col) == QLatin1Char('_')) {
1365                     --col;
1366                 }
1367             }
1368 
1369             if (col > 0 && text.at(col).isUpper()) {
1370                 skipCapsRev(text, col);
1371             }
1372 
1373             for (int i = col; i > 0; --i) {
1374                 if (text.at(i).isUpper() || !text.at(i).isLetterOrNumber()) {
1375                     break;
1376                 }
1377                 --col;
1378             }
1379 
1380             if (col >= 0 && !text.at(col).isLetterOrNumber()) {
1381                 ++col;
1382             }
1383 
1384             if (col < 0) {
1385                 jump = 0;
1386             } else if (col == column() && column() > 0) {
1387                 jump = column() - 1;
1388             } else {
1389                 jump = col;
1390             }
1391 
1392             m_cursor.setColumn(jump);
1393         }
1394 
1395         Q_ASSERT(valid());
1396         return *this;
1397     }
1398 
operator -=(int n)1399     CalculatingCursor &operator-=(int n) override
1400     {
1401         return operator+=(-n);
1402     }
1403 };
1404 
moveChar(KateViewInternal::Bias bias,bool sel)1405 void KateViewInternal::moveChar(KateViewInternal::Bias bias, bool sel)
1406 {
1407     KTextEditor::Cursor c;
1408     if (view()->wrapCursor()) {
1409         c = WrappingCursor(this, m_cursor) += bias;
1410     } else {
1411         c = BoundedCursor(this, m_cursor) += bias;
1412     }
1413 
1414     updateSelection(c, sel);
1415     updateCursor(c);
1416 }
1417 
cursorPrevChar(bool sel)1418 void KateViewInternal::cursorPrevChar(bool sel)
1419 {
1420     if (!view()->wrapCursor() && m_cursor.column() == 0) {
1421         return;
1422     }
1423 
1424     moveChar(KateViewInternal::left, sel);
1425 }
1426 
cursorNextChar(bool sel)1427 void KateViewInternal::cursorNextChar(bool sel)
1428 {
1429     moveChar(KateViewInternal::right, sel);
1430 }
1431 
wordPrev(bool sel)1432 void KateViewInternal::wordPrev(bool sel)
1433 {
1434     WrappingCursor c(this, m_cursor);
1435     // First we skip backwards all space.
1436     // Then we look up into which category the current position falls:
1437     // 1. a "word" character
1438     // 2. a "non-word" character (except space)
1439     // 3. the beginning of the line
1440     // and skip all preceding characters that fall into this class.
1441     // The code assumes that space is never part of the word character class.
1442 
1443     KateHighlighting *h = doc()->highlight();
1444     if (!c.atEdge(left)) {
1445         while (!c.atEdge(left) && doc()->line(c.line())[c.column() - 1].isSpace()) {
1446             --c;
1447         }
1448     }
1449     if (c.atEdge(left)) {
1450         --c;
1451     } else if (h->isInWord(doc()->line(c.line())[c.column() - 1])) {
1452         if (doc()->config()->camelCursor()) {
1453             CamelCursor cc(this, m_cursor);
1454             --cc;
1455             updateSelection(cc, sel);
1456             updateCursor(cc);
1457             return;
1458         } else {
1459             while (!c.atEdge(left) && h->isInWord(doc()->line(c.line())[c.column() - 1])) {
1460                 --c;
1461             }
1462         }
1463     } else {
1464         while (!c.atEdge(left)
1465                && !h->isInWord(doc()->line(c.line())[c.column() - 1])
1466                // in order to stay symmetric to wordLeft()
1467                // we must not skip space preceding a non-word sequence
1468                && !doc()->line(c.line())[c.column() - 1].isSpace()) {
1469             --c;
1470         }
1471     }
1472 
1473     updateSelection(c, sel);
1474     updateCursor(c);
1475 }
1476 
wordNext(bool sel)1477 void KateViewInternal::wordNext(bool sel)
1478 {
1479     WrappingCursor c(this, m_cursor);
1480 
1481     // We look up into which category the current position falls:
1482     // 1. a "word" character
1483     // 2. a "non-word" character (except space)
1484     // 3. the end of the line
1485     // and skip all following characters that fall into this class.
1486     // If the skipped characters are followed by space, we skip that too.
1487     // The code assumes that space is never part of the word character class.
1488 
1489     KateHighlighting *h = doc()->highlight();
1490     if (c.atEdge(right)) {
1491         ++c;
1492     } else if (h->isInWord(doc()->line(c.line())[c.column()])) {
1493         if (doc()->config()->camelCursor()) {
1494             CamelCursor cc(this, m_cursor);
1495             ++cc;
1496             updateSelection(cc, sel);
1497             updateCursor(cc);
1498             return;
1499         } else {
1500             while (!c.atEdge(right) && h->isInWord(doc()->line(c.line())[c.column()])) {
1501                 ++c;
1502             }
1503         }
1504     } else {
1505         while (!c.atEdge(right)
1506                && !h->isInWord(doc()->line(c.line())[c.column()])
1507                // we must not skip space, because if that space is followed
1508                // by more non-word characters, we would skip them, too
1509                && !doc()->line(c.line())[c.column()].isSpace()) {
1510             ++c;
1511         }
1512     }
1513 
1514     while (!c.atEdge(right) && doc()->line(c.line())[c.column()].isSpace()) {
1515         ++c;
1516     }
1517     updateSelection(c, sel);
1518     updateCursor(c);
1519 }
1520 
moveEdge(KateViewInternal::Bias bias,bool sel)1521 void KateViewInternal::moveEdge(KateViewInternal::Bias bias, bool sel)
1522 {
1523     BoundedCursor c(this, m_cursor);
1524     c.toEdge(bias);
1525     updateSelection(c, sel);
1526     updateCursor(c);
1527 }
1528 
home(bool sel)1529 void KateViewInternal::home(bool sel)
1530 {
1531     if (view()->dynWordWrap() && currentLayout().startCol()) {
1532         // Allow us to go to the real start if we're already at the start of the view line
1533         if (m_cursor.column() != currentLayout().startCol()) {
1534             KTextEditor::Cursor c = currentLayout().start();
1535             updateSelection(c, sel);
1536             updateCursor(c);
1537             return;
1538         }
1539     }
1540 
1541     if (!doc()->config()->smartHome()) {
1542         moveEdge(left, sel);
1543         return;
1544     }
1545 
1546     Kate::TextLine l = doc()->kateTextLine(m_cursor.line());
1547 
1548     if (!l) {
1549         return;
1550     }
1551 
1552     KTextEditor::Cursor c = m_cursor;
1553     int lc = l->firstChar();
1554 
1555     if (lc < 0 || c.column() == lc) {
1556         c.setColumn(0);
1557     } else {
1558         c.setColumn(lc);
1559     }
1560 
1561     updateSelection(c, sel);
1562     updateCursor(c, true);
1563 }
1564 
end(bool sel)1565 void KateViewInternal::end(bool sel)
1566 {
1567     KateTextLayout layout = currentLayout();
1568 
1569     if (view()->dynWordWrap() && layout.wrap()) {
1570         // Allow us to go to the real end if we're already at the end of the view line
1571         if (m_cursor.column() < layout.endCol() - 1) {
1572             KTextEditor::Cursor c(m_cursor.line(), layout.endCol() - 1);
1573             updateSelection(c, sel);
1574             updateCursor(c);
1575             return;
1576         }
1577     }
1578 
1579     if (!doc()->config()->smartHome()) {
1580         moveEdge(right, sel);
1581         return;
1582     }
1583 
1584     Kate::TextLine l = doc()->kateTextLine(m_cursor.line());
1585 
1586     if (!l) {
1587         return;
1588     }
1589 
1590     // "Smart End", as requested in bugs #78258 and #106970
1591     if (m_cursor.column() == doc()->lineLength(m_cursor.line())) {
1592         KTextEditor::Cursor c = m_cursor;
1593         c.setColumn(l->lastChar() + 1);
1594         updateSelection(c, sel);
1595         updateCursor(c, true);
1596     } else {
1597         moveEdge(right, sel);
1598     }
1599 }
1600 
currentLayout() const1601 KateTextLayout KateViewInternal::currentLayout() const
1602 {
1603     return cache()->textLayout(m_cursor);
1604 }
1605 
previousLayout() const1606 KateTextLayout KateViewInternal::previousLayout() const
1607 {
1608     int currentViewLine = cache()->viewLine(m_cursor);
1609 
1610     if (currentViewLine) {
1611         return cache()->textLayout(m_cursor.line(), currentViewLine - 1);
1612     } else {
1613         return cache()->textLayout(view()->textFolding().visibleLineToLine(m_displayCursor.line() - 1), -1);
1614     }
1615 }
1616 
nextLayout() const1617 KateTextLayout KateViewInternal::nextLayout() const
1618 {
1619     int currentViewLine = cache()->viewLine(m_cursor) + 1;
1620 
1621     if (currentViewLine >= cache()->line(m_cursor.line())->viewLineCount()) {
1622         currentViewLine = 0;
1623         return cache()->textLayout(view()->textFolding().visibleLineToLine(m_displayCursor.line() + 1), currentViewLine);
1624     } else {
1625         return cache()->textLayout(m_cursor.line(), currentViewLine);
1626     }
1627 }
1628 
1629 /*
1630  * This returns the cursor which is offset by (offset) view lines.
1631  * This is the main function which is called by code not specifically dealing with word-wrap.
1632  * The opposite conversion (cursor to offset) can be done with cache()->displayViewLine().
1633  *
1634  * The cursors involved are virtual cursors (ie. equivalent to m_displayCursor)
1635  */
1636 
viewLineOffset(const KTextEditor::Cursor virtualCursor,int offset,bool keepX)1637 KTextEditor::Cursor KateViewInternal::viewLineOffset(const KTextEditor::Cursor virtualCursor, int offset, bool keepX)
1638 {
1639     if (!view()->dynWordWrap()) {
1640         KTextEditor::Cursor ret(qMin((int)view()->textFolding().visibleLines() - 1, virtualCursor.line() + offset), 0);
1641 
1642         if (ret.line() < 0) {
1643             ret.setLine(0);
1644         }
1645 
1646         if (keepX) {
1647             int realLine = view()->textFolding().visibleLineToLine(ret.line());
1648             KateTextLayout t = cache()->textLayout(realLine, 0);
1649             Q_ASSERT(t.isValid());
1650 
1651             ret.setColumn(renderer()->xToCursor(t, m_preservedX, !view()->wrapCursor()).column());
1652         }
1653 
1654         return ret;
1655     }
1656 
1657     KTextEditor::Cursor realCursor = virtualCursor;
1658     realCursor.setLine(view()->textFolding().visibleLineToLine(view()->textFolding().lineToVisibleLine(virtualCursor.line())));
1659 
1660     int cursorViewLine = cache()->viewLine(realCursor);
1661 
1662     int currentOffset = 0;
1663     int virtualLine = 0;
1664 
1665     bool forwards = (offset > 0) ? true : false;
1666 
1667     if (forwards) {
1668         currentOffset = cache()->lastViewLine(realCursor.line()) - cursorViewLine;
1669         if (offset <= currentOffset) {
1670             // the answer is on the same line
1671             KateTextLayout thisLine = cache()->textLayout(realCursor.line(), cursorViewLine + offset);
1672             Q_ASSERT(thisLine.virtualLine() == (int)view()->textFolding().lineToVisibleLine(virtualCursor.line()));
1673             return KTextEditor::Cursor(virtualCursor.line(), thisLine.startCol());
1674         }
1675 
1676         virtualLine = virtualCursor.line() + 1;
1677 
1678     } else {
1679         offset = -offset;
1680         currentOffset = cursorViewLine;
1681         if (offset <= currentOffset) {
1682             // the answer is on the same line
1683             KateTextLayout thisLine = cache()->textLayout(realCursor.line(), cursorViewLine - offset);
1684             Q_ASSERT(thisLine.virtualLine() == (int)view()->textFolding().lineToVisibleLine(virtualCursor.line()));
1685             return KTextEditor::Cursor(virtualCursor.line(), thisLine.startCol());
1686         }
1687 
1688         virtualLine = virtualCursor.line() - 1;
1689     }
1690 
1691     currentOffset++;
1692 
1693     while (virtualLine >= 0 && virtualLine < (int)view()->textFolding().visibleLines()) {
1694         int realLine = view()->textFolding().visibleLineToLine(virtualLine);
1695         KateLineLayoutPtr thisLine = cache()->line(realLine, virtualLine);
1696         if (!thisLine) {
1697             break;
1698         }
1699 
1700         for (int i = 0; i < thisLine->viewLineCount(); ++i) {
1701             if (offset == currentOffset) {
1702                 KateTextLayout thisViewLine = thisLine->viewLine(i);
1703 
1704                 if (!forwards) {
1705                     // We actually want it the other way around
1706                     int requiredViewLine = cache()->lastViewLine(realLine) - thisViewLine.viewLine();
1707                     if (requiredViewLine != thisViewLine.viewLine()) {
1708                         thisViewLine = thisLine->viewLine(requiredViewLine);
1709                     }
1710                 }
1711 
1712                 KTextEditor::Cursor ret(virtualLine, thisViewLine.startCol());
1713 
1714                 // keep column position
1715                 if (keepX) {
1716                     KTextEditor::Cursor realCursor = toRealCursor(virtualCursor);
1717                     KateTextLayout t = cache()->textLayout(realCursor);
1718                     // renderer()->cursorToX(t, realCursor, !view()->wrapCursor());
1719 
1720                     realCursor = renderer()->xToCursor(thisViewLine, m_preservedX, !view()->wrapCursor());
1721                     ret.setColumn(realCursor.column());
1722                 }
1723 
1724                 return ret;
1725             }
1726 
1727             currentOffset++;
1728         }
1729 
1730         if (forwards) {
1731             virtualLine++;
1732         } else {
1733             virtualLine--;
1734         }
1735     }
1736 
1737     // Looks like we were asked for something a bit exotic.
1738     // Return the max/min valid position.
1739     if (forwards) {
1740         return KTextEditor::Cursor(view()->textFolding().visibleLines() - 1,
1741                                    doc()->lineLength(view()->textFolding().visibleLineToLine(view()->textFolding().visibleLines() - 1)));
1742     } else {
1743         return KTextEditor::Cursor(0, 0);
1744     }
1745 }
1746 
lineMaxCursorX(const KateTextLayout & range)1747 int KateViewInternal::lineMaxCursorX(const KateTextLayout &range)
1748 {
1749     if (!view()->wrapCursor() && !range.wrap()) {
1750         return INT_MAX;
1751     }
1752 
1753     int maxX = range.endX();
1754 
1755     if (maxX && range.wrap()) {
1756         QChar lastCharInLine = doc()->kateTextLine(range.line())->at(range.endCol() - 1);
1757         maxX -= renderer()->currentFontMetrics().horizontalAdvance(lastCharInLine);
1758     }
1759 
1760     return maxX;
1761 }
1762 
lineMaxCol(const KateTextLayout & range)1763 int KateViewInternal::lineMaxCol(const KateTextLayout &range)
1764 {
1765     int maxCol = range.endCol();
1766 
1767     if (maxCol && range.wrap()) {
1768         maxCol--;
1769     }
1770 
1771     return maxCol;
1772 }
1773 
cursorUp(bool sel)1774 void KateViewInternal::cursorUp(bool sel)
1775 {
1776     if (!sel && view()->completionWidget()->isCompletionActive()) {
1777         view()->completionWidget()->cursorUp();
1778         return;
1779     }
1780 
1781     // assert that the display cursor is in visible lines
1782     Q_ASSERT(m_displayCursor.line() < view()->textFolding().visibleLines());
1783 
1784     // move cursor to start of line, if we are at first line!
1785     if (m_displayCursor.line() == 0 && (!view()->dynWordWrap() || cache()->viewLine(m_cursor) == 0)) {
1786         home(sel);
1787         return;
1788     }
1789 
1790     m_preserveX = true;
1791 
1792     KateTextLayout thisLine = currentLayout();
1793     // This is not the first line because that is already simplified out above
1794     KateTextLayout pRange = previousLayout();
1795 
1796     // Ensure we're in the right spot
1797     Q_ASSERT(m_cursor.line() == thisLine.line());
1798     Q_ASSERT(m_cursor.column() >= thisLine.startCol());
1799     Q_ASSERT(!thisLine.wrap() || m_cursor.column() < thisLine.endCol());
1800 
1801     KTextEditor::Cursor c = renderer()->xToCursor(pRange, m_preservedX, !view()->wrapCursor());
1802 
1803     updateSelection(c, sel);
1804     updateCursor(c);
1805 }
1806 
cursorDown(bool sel)1807 void KateViewInternal::cursorDown(bool sel)
1808 {
1809     if (!sel && view()->completionWidget()->isCompletionActive()) {
1810         view()->completionWidget()->cursorDown();
1811         return;
1812     }
1813 
1814     // move cursor to end of line, if we are at last line!
1815     if ((m_displayCursor.line() >= view()->textFolding().visibleLines() - 1)
1816         && (!view()->dynWordWrap() || cache()->viewLine(m_cursor) == cache()->lastViewLine(m_cursor.line()))) {
1817         end(sel);
1818         return;
1819     }
1820 
1821     m_preserveX = true;
1822 
1823     KateTextLayout thisLine = currentLayout();
1824     // This is not the last line because that is already simplified out above
1825     KateTextLayout nRange = nextLayout();
1826 
1827     // Ensure we're in the right spot
1828     Q_ASSERT((m_cursor.line() == thisLine.line()) && (m_cursor.column() >= thisLine.startCol()) && (!thisLine.wrap() || m_cursor.column() < thisLine.endCol()));
1829 
1830     KTextEditor::Cursor c = renderer()->xToCursor(nRange, m_preservedX, !view()->wrapCursor());
1831 
1832     updateSelection(c, sel);
1833     updateCursor(c);
1834 }
1835 
cursorToMatchingBracket(bool sel)1836 void KateViewInternal::cursorToMatchingBracket(bool sel)
1837 {
1838     KTextEditor::Cursor c = findMatchingBracket();
1839 
1840     if (c.isValid()) {
1841         updateSelection(c, sel);
1842         updateCursor(c);
1843     }
1844 }
1845 
topOfView(bool sel)1846 void KateViewInternal::topOfView(bool sel)
1847 {
1848     KTextEditor::Cursor c = viewLineOffset(startPos(), m_minLinesVisible);
1849     updateSelection(toRealCursor(c), sel);
1850     updateCursor(toRealCursor(c));
1851 }
1852 
bottomOfView(bool sel)1853 void KateViewInternal::bottomOfView(bool sel)
1854 {
1855     KTextEditor::Cursor c = viewLineOffset(endPos(), -m_minLinesVisible);
1856     updateSelection(toRealCursor(c), sel);
1857     updateCursor(toRealCursor(c));
1858 }
1859 
1860 // lines is the offset to scroll by
scrollLines(int lines,bool sel)1861 void KateViewInternal::scrollLines(int lines, bool sel)
1862 {
1863     KTextEditor::Cursor c = viewLineOffset(m_displayCursor, lines, true);
1864 
1865     // Fix the virtual cursor -> real cursor
1866     c.setLine(view()->textFolding().visibleLineToLine(c.line()));
1867 
1868     updateSelection(c, sel);
1869     updateCursor(c);
1870 }
1871 
1872 // This is a bit misleading... it's asking for the view to be scrolled, not the cursor
scrollUp()1873 void KateViewInternal::scrollUp()
1874 {
1875     KTextEditor::Cursor newPos = viewLineOffset(startPos(), -1);
1876     scrollPos(newPos);
1877 }
1878 
scrollDown()1879 void KateViewInternal::scrollDown()
1880 {
1881     KTextEditor::Cursor newPos = viewLineOffset(startPos(), 1);
1882     scrollPos(newPos);
1883 }
1884 
setAutoCenterLines(int viewLines,bool updateView)1885 void KateViewInternal::setAutoCenterLines(int viewLines, bool updateView)
1886 {
1887     m_autoCenterLines = viewLines;
1888     m_minLinesVisible = qMin(int((linesDisplayed() - 1) / 2), m_autoCenterLines);
1889     if (updateView) {
1890         KateViewInternal::updateView();
1891     }
1892 }
1893 
pageUp(bool sel,bool half)1894 void KateViewInternal::pageUp(bool sel, bool half)
1895 {
1896     if (view()->isCompletionActive()) {
1897         view()->completionWidget()->pageUp();
1898         return;
1899     }
1900 
1901     // remember the view line and x pos
1902     int viewLine = cache()->displayViewLine(m_displayCursor);
1903     bool atTop = startPos().atStartOfDocument();
1904 
1905     // Adjust for an auto-centering cursor
1906     int lineadj = m_minLinesVisible;
1907 
1908     int linesToScroll;
1909     if (!half) {
1910         linesToScroll = -qMax((linesDisplayed() - 1) - lineadj, 0);
1911     } else {
1912         linesToScroll = -qMax((linesDisplayed() / 2 - 1) - lineadj, 0);
1913     }
1914 
1915     m_preserveX = true;
1916 
1917     if (!doc()->pageUpDownMovesCursor() && !atTop) {
1918         KTextEditor::Cursor newStartPos = viewLineOffset(startPos(), linesToScroll - 1);
1919         scrollPos(newStartPos);
1920 
1921         // put the cursor back approximately where it was
1922         KTextEditor::Cursor newPos = toRealCursor(viewLineOffset(newStartPos, viewLine, true));
1923 
1924         KateTextLayout newLine = cache()->textLayout(newPos);
1925 
1926         newPos = renderer()->xToCursor(newLine, m_preservedX, !view()->wrapCursor());
1927 
1928         m_preserveX = true;
1929         updateSelection(newPos, sel);
1930         updateCursor(newPos);
1931 
1932     } else {
1933         scrollLines(linesToScroll, sel);
1934     }
1935 }
1936 
pageDown(bool sel,bool half)1937 void KateViewInternal::pageDown(bool sel, bool half)
1938 {
1939     if (view()->isCompletionActive()) {
1940         view()->completionWidget()->pageDown();
1941         return;
1942     }
1943 
1944     // remember the view line
1945     int viewLine = cache()->displayViewLine(m_displayCursor);
1946     bool atEnd = startPos() >= m_cachedMaxStartPos;
1947 
1948     // Adjust for an auto-centering cursor
1949     int lineadj = m_minLinesVisible;
1950 
1951     int linesToScroll;
1952     if (!half) {
1953         linesToScroll = qMax((linesDisplayed() - 1) - lineadj, 0);
1954     } else {
1955         linesToScroll = qMax((linesDisplayed() / 2 - 1) - lineadj, 0);
1956     }
1957 
1958     m_preserveX = true;
1959 
1960     if (!doc()->pageUpDownMovesCursor() && !atEnd) {
1961         KTextEditor::Cursor newStartPos = viewLineOffset(startPos(), linesToScroll + 1);
1962         scrollPos(newStartPos);
1963 
1964         // put the cursor back approximately where it was
1965         KTextEditor::Cursor newPos = toRealCursor(viewLineOffset(newStartPos, viewLine, true));
1966 
1967         KateTextLayout newLine = cache()->textLayout(newPos);
1968 
1969         newPos = renderer()->xToCursor(newLine, m_preservedX, !view()->wrapCursor());
1970 
1971         m_preserveX = true;
1972         updateSelection(newPos, sel);
1973         updateCursor(newPos);
1974 
1975     } else {
1976         scrollLines(linesToScroll, sel);
1977     }
1978 }
1979 
maxLen(int startLine)1980 int KateViewInternal::maxLen(int startLine)
1981 {
1982     Q_ASSERT(!view()->dynWordWrap());
1983 
1984     int displayLines = (view()->height() / renderer()->lineHeight()) + 1;
1985 
1986     int maxLen = 0;
1987 
1988     for (int z = 0; z < displayLines; z++) {
1989         int virtualLine = startLine + z;
1990 
1991         if (virtualLine < 0 || virtualLine >= (int)view()->textFolding().visibleLines()) {
1992             break;
1993         }
1994 
1995         maxLen = qMax(maxLen, cache()->line(view()->textFolding().visibleLineToLine(virtualLine))->width());
1996     }
1997 
1998     return maxLen;
1999 }
2000 
columnScrollingPossible()2001 bool KateViewInternal::columnScrollingPossible()
2002 {
2003     return !view()->dynWordWrap() && m_columnScroll->isEnabled() && (m_columnScroll->maximum() > 0);
2004 }
2005 
lineScrollingPossible()2006 bool KateViewInternal::lineScrollingPossible()
2007 {
2008     return m_lineScroll->minimum() != m_lineScroll->maximum();
2009 }
2010 
top(bool sel)2011 void KateViewInternal::top(bool sel)
2012 {
2013     KTextEditor::Cursor newCursor(0, 0);
2014 
2015     newCursor = renderer()->xToCursor(cache()->textLayout(newCursor), m_preservedX, !view()->wrapCursor());
2016 
2017     updateSelection(newCursor, sel);
2018     updateCursor(newCursor);
2019 }
2020 
bottom(bool sel)2021 void KateViewInternal::bottom(bool sel)
2022 {
2023     KTextEditor::Cursor newCursor(doc()->lastLine(), 0);
2024 
2025     newCursor = renderer()->xToCursor(cache()->textLayout(newCursor), m_preservedX, !view()->wrapCursor());
2026 
2027     updateSelection(newCursor, sel);
2028     updateCursor(newCursor);
2029 }
2030 
top_home(bool sel)2031 void KateViewInternal::top_home(bool sel)
2032 {
2033     if (view()->isCompletionActive()) {
2034         view()->completionWidget()->top();
2035         return;
2036     }
2037 
2038     KTextEditor::Cursor c(0, 0);
2039     updateSelection(c, sel);
2040     updateCursor(c);
2041 }
2042 
bottom_end(bool sel)2043 void KateViewInternal::bottom_end(bool sel)
2044 {
2045     if (view()->isCompletionActive()) {
2046         view()->completionWidget()->bottom();
2047         return;
2048     }
2049 
2050     KTextEditor::Cursor c(doc()->lastLine(), doc()->lineLength(doc()->lastLine()));
2051     updateSelection(c, sel);
2052     updateCursor(c);
2053 }
2054 
updateSelection(const KTextEditor::Cursor _newCursor,bool keepSel)2055 void KateViewInternal::updateSelection(const KTextEditor::Cursor _newCursor, bool keepSel)
2056 {
2057     KTextEditor::Cursor newCursor = _newCursor;
2058     if (keepSel) {
2059         if (!view()->selection()
2060             || (m_selectAnchor.line() == -1)
2061             // don't kill the selection if we have a persistent selection and
2062             // the cursor is inside or at the boundaries of the selected area
2063             || (view()->config()->persistentSelection()
2064                 && !(view()->selectionRange().contains(m_cursor) || view()->selectionRange().boundaryAtCursor(m_cursor)))) {
2065             m_selectAnchor = m_cursor;
2066             setSelection(KTextEditor::Range(m_cursor, newCursor));
2067         } else {
2068             bool doSelect = true;
2069             switch (m_selectionMode) {
2070             case Word: {
2071                 // Restore selStartCached if needed. It gets nuked by
2072                 // viewSelectionChanged if we drag the selection into non-existence,
2073                 // which can legitimately happen if a shift+DC selection is unable to
2074                 // set a "proper" (i.e. non-empty) cached selection, e.g. because the
2075                 // start was on something that isn't a word. Word select mode relies
2076                 // on the cached selection being set properly, even if it is empty
2077                 // (i.e. selStartCached == selEndCached).
2078                 if (!m_selectionCached.isValid()) {
2079                     m_selectionCached.setStart(m_selectionCached.end());
2080                 }
2081 
2082                 int c;
2083                 if (newCursor > m_selectionCached.start()) {
2084                     m_selectAnchor = m_selectionCached.start();
2085 
2086                     Kate::TextLine l = doc()->kateTextLine(newCursor.line());
2087 
2088                     c = newCursor.column();
2089                     if (c > 0 && doc()->highlight()->isInWord(l->at(c - 1))) {
2090                         for (; c < l->length(); c++) {
2091                             if (!doc()->highlight()->isInWord(l->at(c))) {
2092                                 break;
2093                             }
2094                         }
2095                     }
2096 
2097                     newCursor.setColumn(c);
2098                 } else if (newCursor < m_selectionCached.start()) {
2099                     m_selectAnchor = m_selectionCached.end();
2100 
2101                     Kate::TextLine l = doc()->kateTextLine(newCursor.line());
2102 
2103                     c = newCursor.column();
2104                     if (c > 0 && c < doc()->lineLength(newCursor.line()) && doc()->highlight()->isInWord(l->at(c))
2105                         && doc()->highlight()->isInWord(l->at(c - 1))) {
2106                         for (c -= 2; c >= 0; c--) {
2107                             if (!doc()->highlight()->isInWord(l->at(c))) {
2108                                 break;
2109                             }
2110                         }
2111                         newCursor.setColumn(c + 1);
2112                     }
2113                 } else {
2114                     doSelect = false;
2115                 }
2116 
2117             } break;
2118             case Line:
2119                 if (!m_selectionCached.isValid()) {
2120                     m_selectionCached = KTextEditor::Range(endLine(), 0, endLine(), 0);
2121                 }
2122                 if (newCursor.line() > m_selectionCached.start().line()) {
2123                     if (newCursor.line() + 1 >= doc()->lines()) {
2124                         newCursor.setColumn(doc()->line(newCursor.line()).length());
2125                     } else {
2126                         newCursor.setPosition(newCursor.line() + 1, 0);
2127                     }
2128                     // Grow to include the entire line
2129                     m_selectAnchor = m_selectionCached.start();
2130                     m_selectAnchor.setColumn(0);
2131                 } else if (newCursor.line() < m_selectionCached.start().line()) {
2132                     newCursor.setColumn(0);
2133                     // Grow to include entire line
2134                     m_selectAnchor = m_selectionCached.end();
2135                     if (m_selectAnchor.column() > 0) {
2136                         if (m_selectAnchor.line() + 1 >= doc()->lines()) {
2137                             m_selectAnchor.setColumn(doc()->line(newCursor.line()).length());
2138                         } else {
2139                             m_selectAnchor.setPosition(m_selectAnchor.line() + 1, 0);
2140                         }
2141                     }
2142                 } else { // same line, ignore
2143                     doSelect = false;
2144                 }
2145                 break;
2146             case Mouse: {
2147                 if (!m_selectionCached.isValid()) {
2148                     break;
2149                 }
2150 
2151                 if (newCursor > m_selectionCached.end()) {
2152                     m_selectAnchor = m_selectionCached.start();
2153                 } else if (newCursor < m_selectionCached.start()) {
2154                     m_selectAnchor = m_selectionCached.end();
2155                 } else {
2156                     doSelect = false;
2157                 }
2158             } break;
2159             default: /* nothing special to do */;
2160             }
2161 
2162             if (doSelect) {
2163                 setSelection(KTextEditor::Range(m_selectAnchor, newCursor));
2164             } else if (m_selectionCached.isValid()) { // we have a cached selection, so we restore that
2165                 setSelection(m_selectionCached);
2166             }
2167         }
2168 
2169         m_selChangedByUser = true;
2170     } else if (!view()->config()->persistentSelection()) {
2171         view()->clearSelection();
2172 
2173         m_selectionCached = KTextEditor::Range::invalid();
2174         m_selectAnchor = KTextEditor::Cursor::invalid();
2175     }
2176 
2177 #ifndef QT_NO_ACCESSIBILITY
2178 //    FIXME KF5
2179 //    QAccessibleTextSelectionEvent ev(this, /* selection start, selection end*/);
2180 //    QAccessible::updateAccessibility(&ev);
2181 #endif
2182 }
2183 
setSelection(const KTextEditor::Range & range)2184 void KateViewInternal::setSelection(const KTextEditor::Range &range)
2185 {
2186     disconnect(m_view, &KTextEditor::ViewPrivate::selectionChanged, this, &KateViewInternal::viewSelectionChanged);
2187     view()->setSelection(range);
2188     connect(m_view, &KTextEditor::ViewPrivate::selectionChanged, this, &KateViewInternal::viewSelectionChanged);
2189 }
2190 
moveCursorToSelectionEdge()2191 void KateViewInternal::moveCursorToSelectionEdge()
2192 {
2193     if (!view()->selection()) {
2194         return;
2195     }
2196 
2197     int tmp = m_minLinesVisible;
2198     m_minLinesVisible = 0;
2199 
2200     if (view()->selectionRange().start() < m_selectAnchor) {
2201         updateCursor(view()->selectionRange().start());
2202     } else {
2203         updateCursor(view()->selectionRange().end());
2204     }
2205 
2206     m_minLinesVisible = tmp;
2207 }
2208 
findMatchingFoldingMarker(const KTextEditor::Cursor currentCursorPos,const int value,const int maxLines)2209 KTextEditor::Range KateViewInternal::findMatchingFoldingMarker(const KTextEditor::Cursor currentCursorPos, const int value, const int maxLines)
2210 {
2211     const int direction = !(value < 0) ? 1 : -1;
2212     int foldCounter = 0;
2213     int lineCounter = 0;
2214     auto &foldMarkers = m_view->doc()->buffer().plainLine(currentCursorPos.line())->foldings();
2215 
2216     // searching a end folding marker? go left to right
2217     // otherwise, go right to left
2218     long i = direction == 1 ? 0 : (long)foldMarkers.size() - 1;
2219 
2220     // For the first line, we start considering the first folding after the cursor
2221     for (; i >= 0 && i < (long)foldMarkers.size(); i += direction) {
2222         if ((foldMarkers[i].offset - currentCursorPos.column()) * direction > 0) {
2223             if (foldMarkers[i].foldingValue == value) {
2224                 foldCounter += 1;
2225             } else if (foldMarkers[i].foldingValue == -value && foldCounter > 0) {
2226                 foldCounter -= 1;
2227             } else if (foldMarkers[i].foldingValue == -value && foldCounter == 0) {
2228                 return KTextEditor::Range(currentCursorPos.line(),
2229                                           getStartOffset(direction, foldMarkers[i].offset, foldMarkers[i].length),
2230                                           currentCursorPos.line(),
2231                                           getEndOffset(direction, foldMarkers[i].offset, foldMarkers[i].length));
2232             }
2233         }
2234     }
2235 
2236     // for the other lines
2237     int currentLine = currentCursorPos.line() + direction;
2238     for (; currentLine >= 0 && currentLine < m_view->doc()->lines() && lineCounter < maxLines; currentLine += direction) {
2239         // update line attributes
2240         auto &foldMarkers = m_view->doc()->buffer().plainLine(currentLine)->foldings();
2241         i = direction == 1 ? 0 : (long)foldMarkers.size() - 1;
2242 
2243         // iterate through the markers
2244         for (; i >= 0 && i < (long)foldMarkers.size(); i += direction) {
2245             if (foldMarkers[i].foldingValue == value) {
2246                 foldCounter += 1;
2247             } else if (foldMarkers[i].foldingValue == -value && foldCounter != 0) {
2248                 foldCounter -= 1;
2249             } else if (foldMarkers[i].foldingValue == -value && foldCounter == 0) {
2250                 return KTextEditor::Range(currentLine,
2251                                           getStartOffset(direction, foldMarkers[i].offset, foldMarkers[i].length),
2252                                           currentLine,
2253                                           getEndOffset(direction, foldMarkers[i].offset, foldMarkers[i].length));
2254             }
2255         }
2256         lineCounter += 1;
2257     }
2258 
2259     // got out of loop, no matching folding found
2260     // returns a invalid folding range
2261     return KTextEditor::Range::invalid();
2262 }
2263 
updateFoldingMarkersHighlighting()2264 void KateViewInternal::updateFoldingMarkersHighlighting()
2265 {
2266     auto &foldings = m_view->doc()->buffer().plainLine(m_cursor.line())->foldings();
2267 
2268     for (unsigned long i = 0; i < foldings.size(); i++) {
2269         // 1 -> left to right, the current folding is start type
2270         // -1 -> right to left, the current folding is end type
2271         int direction = !(foldings[i].foldingValue < 0) ? 1 : -1;
2272 
2273         int startOffset = getStartOffset(-direction, foldings[i].offset, foldings[i].length);
2274         int endOffset = getEndOffset(-direction, foldings[i].offset, foldings[i].length);
2275 
2276         if (m_cursor.column() >= startOffset && m_cursor.column() <= endOffset) {
2277             const auto foldingMarkerMatch = findMatchingFoldingMarker(KTextEditor::Cursor(m_cursor.line(), m_cursor.column()), foldings[i].foldingValue, 2000);
2278 
2279             if (!foldingMarkerMatch.isValid()) {
2280                 break;
2281             }
2282 
2283             // set fmStart to Opening Folding Marker and fmEnd to Ending Folding Marker
2284             if (direction == 1) {
2285                 m_fmStart->setRange(KTextEditor::Range(m_cursor.line(), startOffset, m_cursor.line(), endOffset));
2286                 m_fmEnd->setRange(foldingMarkerMatch);
2287             } else {
2288                 m_fmStart->setRange(foldingMarkerMatch);
2289                 m_fmEnd->setRange(KTextEditor::Range(m_cursor.line(), startOffset, m_cursor.line(), endOffset));
2290             }
2291 
2292             KTextEditor::Attribute::Ptr fill = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute());
2293             fill->setBackground(view()->m_renderer->config()->highlightedBracketColor());
2294 
2295             m_fmStart->setAttribute(fill);
2296             m_fmEnd->setAttribute(fill);
2297             return;
2298         }
2299     }
2300     m_fmStart->setRange(KTextEditor::Range::invalid());
2301     m_fmEnd->setRange(KTextEditor::Range::invalid());
2302 }
2303 
updateCursor(const KTextEditor::Cursor newCursor,bool force,bool center,bool calledExternally)2304 void KateViewInternal::updateCursor(const KTextEditor::Cursor newCursor, bool force, bool center, bool calledExternally)
2305 {
2306     if (!force && (m_cursor.toCursor() == newCursor)) {
2307         m_displayCursor = toVirtualCursor(newCursor);
2308         if (!m_madeVisible && m_view == doc()->activeView()) {
2309             // unfold if required
2310             view()->textFolding().ensureLineIsVisible(newCursor.line());
2311 
2312             makeVisible(m_displayCursor, m_displayCursor.column(), false, center, calledExternally);
2313         }
2314 
2315         return;
2316     }
2317 
2318     if (m_cursor.line() != newCursor.line()) {
2319         m_leftBorder->updateForCursorLineChange();
2320     }
2321 
2322     // unfold if required
2323     view()->textFolding().ensureLineIsVisible(newCursor.line());
2324 
2325     KTextEditor::Cursor oldDisplayCursor = m_displayCursor;
2326 
2327     m_displayCursor = toVirtualCursor(newCursor);
2328     m_cursor.setPosition(newCursor);
2329 
2330     if (m_view == doc()->activeView()) {
2331         makeVisible(m_displayCursor, m_displayCursor.column(), false, center, calledExternally);
2332     }
2333 
2334     updateBracketMarks();
2335 
2336     updateFoldingMarkersHighlighting();
2337 
2338     // avoid double work, tagLine => tagLines => not that cheap, much more costly than to compare 2 ints
2339     tagLine(oldDisplayCursor);
2340     if (oldDisplayCursor.line() != m_displayCursor.line()) {
2341         tagLine(m_displayCursor);
2342     }
2343 
2344     updateMicroFocus();
2345 
2346     if (m_cursorTimer.isActive()) {
2347         if (QApplication::cursorFlashTime() > 0) {
2348             m_cursorTimer.start(QApplication::cursorFlashTime() / 2);
2349         }
2350         renderer()->setDrawCaret(true);
2351     }
2352 
2353     // Remember the maximum X position if requested
2354     if (m_preserveX) {
2355         m_preserveX = false;
2356     } else {
2357         m_preservedX = renderer()->cursorToX(cache()->textLayout(m_cursor), m_cursor, !view()->wrapCursor());
2358     }
2359 
2360     // qCDebug(LOG_KTE) << "m_preservedX: " << m_preservedX << " (was "<< oldmaxx << "), m_cursorX: " << m_cursorX;
2361     // qCDebug(LOG_KTE) << "Cursor now located at real " << cursor.line << "," << cursor.col << ", virtual " << m_displayCursor.line << ", " <<
2362     // m_displayCursor.col << "; Top is " << startLine() << ", " << startPos().col;
2363 
2364     cursorMoved();
2365 
2366     updateDirty(); // paintText(0, 0, width(), height(), true);
2367 
2368     Q_EMIT view()->cursorPositionChanged(m_view, m_cursor);
2369 }
2370 
updateBracketMarkAttributes()2371 void KateViewInternal::updateBracketMarkAttributes()
2372 {
2373     KTextEditor::Attribute::Ptr bracketFill = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute());
2374     bracketFill->setBackground(view()->m_renderer->config()->highlightedBracketColor());
2375     bracketFill->setBackgroundFillWhitespace(false);
2376     if (QFontInfo(renderer()->currentFont()).fixedPitch()) {
2377         // make font bold only for fixed fonts, otherwise text jumps around
2378         bracketFill->setFontBold();
2379     }
2380 
2381     m_bmStart->setAttribute(bracketFill);
2382     m_bmEnd->setAttribute(bracketFill);
2383 
2384     if (view()->m_renderer->config()->showWholeBracketExpression()) {
2385         KTextEditor::Attribute::Ptr expressionFill = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute());
2386         expressionFill->setBackground(view()->m_renderer->config()->highlightedBracketColor());
2387         expressionFill->setBackgroundFillWhitespace(false);
2388 
2389         m_bm->setAttribute(expressionFill);
2390     } else {
2391         m_bm->setAttribute(KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()));
2392     }
2393 }
2394 
updateBracketMarks()2395 void KateViewInternal::updateBracketMarks()
2396 {
2397     // add some limit to this, this is really endless on big files without limit
2398     const int maxLines = 5000;
2399     const KTextEditor::Range newRange = doc()->findMatchingBracket(m_cursor, maxLines);
2400 
2401     // new range valid, then set ranges to it
2402     if (newRange.isValid()) {
2403         if (m_bm->toRange() == newRange) {
2404             // hide preview as it now (probably) blocks the top of the view
2405             hideBracketMatchPreview();
2406             return;
2407         }
2408 
2409         // modify full range
2410         m_bm->setRange(newRange);
2411 
2412         // modify start and end ranges
2413         m_bmStart->setRange(KTextEditor::Range(m_bm->start(), KTextEditor::Cursor(m_bm->start().line(), m_bm->start().column() + 1)));
2414         m_bmEnd->setRange(KTextEditor::Range(m_bm->end(), KTextEditor::Cursor(m_bm->end().line(), m_bm->end().column() + 1)));
2415 
2416         // show preview of the matching bracket's line
2417         if (m_view->config()->value(KateViewConfig::ShowBracketMatchPreview).toBool()) {
2418             showBracketMatchPreview();
2419         }
2420 
2421         // flash matching bracket
2422         if (!renderer()->config()->animateBracketMatching()) {
2423             return;
2424         }
2425 
2426         const KTextEditor::Cursor flashPos = (m_cursor == m_bmStart->start() || m_cursor == m_bmStart->end()) ? m_bmEnd->start() : m_bm->start();
2427         if (flashPos != m_bmLastFlashPos->toCursor()) {
2428             m_bmLastFlashPos->setPosition(flashPos);
2429 
2430             KTextEditor::Attribute::Ptr attribute = attributeAt(flashPos);
2431             attribute->setBackground(view()->m_renderer->config()->highlightedBracketColor());
2432             attribute->setFontBold(m_bmStart->attribute()->fontBold());
2433 
2434             flashChar(flashPos, attribute);
2435         }
2436         return;
2437     }
2438 
2439     // new range was invalid
2440     m_bm->setRange(KTextEditor::Range::invalid());
2441     m_bmStart->setRange(KTextEditor::Range::invalid());
2442     m_bmEnd->setRange(KTextEditor::Range::invalid());
2443     m_bmLastFlashPos->setPosition(KTextEditor::Cursor::invalid());
2444     hideBracketMatchPreview();
2445 }
2446 
tagLine(const KTextEditor::Cursor virtualCursor)2447 bool KateViewInternal::tagLine(const KTextEditor::Cursor virtualCursor)
2448 {
2449     // we had here some special case handling for one line, it was just randomly wrong for dyn. word wrapped stuff => use the generic function
2450     return tagLines(virtualCursor, virtualCursor, false);
2451 }
2452 
tagLines(int start,int end,bool realLines)2453 bool KateViewInternal::tagLines(int start, int end, bool realLines)
2454 {
2455     return tagLines(KTextEditor::Cursor(start, 0), KTextEditor::Cursor(end, -1), realLines);
2456 }
2457 
tagLines(KTextEditor::Cursor start,KTextEditor::Cursor end,bool realCursors)2458 bool KateViewInternal::tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end, bool realCursors)
2459 {
2460     if (realCursors) {
2461         cache()->relayoutLines(start.line(), end.line());
2462 
2463         // qCDebug(LOG_KTE)<<"realLines is true";
2464         start = toVirtualCursor(start);
2465         end = toVirtualCursor(end);
2466 
2467     } else {
2468         cache()->relayoutLines(toRealCursor(start).line(), toRealCursor(end).line());
2469     }
2470 
2471     if (end.line() < startLine()) {
2472         // qCDebug(LOG_KTE)<<"end<startLine";
2473         return false;
2474     }
2475     // Used to be > endLine(), but cache may not be valid when checking, so use a
2476     // less optimal but still adequate approximation (potential overestimation but minimal performance difference)
2477     if (start.line() > startLine() + cache()->viewCacheLineCount()) {
2478         // qCDebug(LOG_KTE)<<"start> endLine"<<start<<" "<<(endLine());
2479         return false;
2480     }
2481 
2482     cache()->updateViewCache(startPos());
2483 
2484     // qCDebug(LOG_KTE) << "tagLines( [" << start << "], [" << end << "] )";
2485 
2486     bool ret = false;
2487 
2488     for (int z = 0; z < cache()->viewCacheLineCount(); z++) {
2489         KateTextLayout &line = cache()->viewLine(z);
2490         if ((line.virtualLine() > start.line() || (line.virtualLine() == start.line() && line.endCol() >= start.column() && start.column() != -1))
2491             && (line.virtualLine() < end.line() || (line.virtualLine() == end.line() && (line.startCol() <= end.column() || end.column() == -1)))) {
2492             ret = true;
2493             break;
2494             // qCDebug(LOG_KTE) << "Tagged line " << line.line();
2495         }
2496     }
2497 
2498     if (!view()->dynWordWrap()) {
2499         int y = lineToY(start.line());
2500         // FIXME is this enough for when multiple lines are deleted
2501         int h = (end.line() - start.line() + 2) * renderer()->lineHeight();
2502         if (end.line() >= view()->textFolding().visibleLines() - 1) {
2503             h = height();
2504         }
2505 
2506         m_leftBorder->update(0, y, m_leftBorder->width(), h);
2507     } else {
2508         // FIXME Do we get enough good info in editRemoveText to optimize this more?
2509         // bool justTagged = false;
2510         for (int z = 0; z < cache()->viewCacheLineCount(); z++) {
2511             KateTextLayout &line = cache()->viewLine(z);
2512             if (!line.isValid()
2513                 || ((line.virtualLine() > start.line() || (line.virtualLine() == start.line() && line.endCol() >= start.column() && start.column() != -1))
2514                     && (line.virtualLine() < end.line() || (line.virtualLine() == end.line() && (line.startCol() <= end.column() || end.column() == -1))))) {
2515                 // justTagged = true;
2516                 m_leftBorder->update(0, z * renderer()->lineHeight(), m_leftBorder->width(), m_leftBorder->height());
2517                 break;
2518             }
2519             /*else if (justTagged)
2520             {
2521               justTagged = false;
2522               leftBorder->update (0, z * doc()->viewFont.fontHeight, leftBorder->width(), doc()->viewFont.fontHeight);
2523               break;
2524             }*/
2525         }
2526     }
2527 
2528     return ret;
2529 }
2530 
tagRange(const KTextEditor::Range & range,bool realCursors)2531 bool KateViewInternal::tagRange(const KTextEditor::Range &range, bool realCursors)
2532 {
2533     return tagLines(range.start(), range.end(), realCursors);
2534 }
2535 
tagAll()2536 void KateViewInternal::tagAll()
2537 {
2538     // clear the cache...
2539     cache()->clear();
2540 
2541     m_leftBorder->updateFont();
2542     m_leftBorder->update();
2543 }
2544 
paintCursor()2545 void KateViewInternal::paintCursor()
2546 {
2547     if (tagLine(m_displayCursor)) {
2548         updateDirty(); // paintText (0,0,width(), height(), true);
2549     }
2550 }
2551 
2552 // Point in content coordinates
placeCursor(const QPoint & p,bool keepSelection,bool updateSelection)2553 void KateViewInternal::placeCursor(const QPoint &p, bool keepSelection, bool updateSelection)
2554 {
2555     KateTextLayout thisLine = yToKateTextLayout(p.y());
2556     KTextEditor::Cursor c;
2557 
2558     if (!thisLine.isValid()) { // probably user clicked below the last line -> use the last line
2559         thisLine = cache()->textLayout(doc()->lines() - 1, -1);
2560     }
2561 
2562     c = renderer()->xToCursor(thisLine, startX() + p.x(), !view()->wrapCursor());
2563 
2564     if (c.line() < 0 || c.line() >= doc()->lines()) {
2565         return;
2566     }
2567 
2568     if (updateSelection) {
2569         KateViewInternal::updateSelection(c, keepSelection);
2570     }
2571 
2572     int tmp = m_minLinesVisible;
2573     m_minLinesVisible = 0;
2574     updateCursor(c);
2575     m_minLinesVisible = tmp;
2576 
2577     if (updateSelection && keepSelection) {
2578         moveCursorToSelectionEdge();
2579     }
2580 }
2581 
2582 // Point in content coordinates
isTargetSelected(const QPoint & p)2583 bool KateViewInternal::isTargetSelected(const QPoint &p)
2584 {
2585     const KateTextLayout &thisLine = yToKateTextLayout(p.y());
2586     if (!thisLine.isValid()) {
2587         return false;
2588     }
2589 
2590     return view()->cursorSelected(renderer()->xToCursor(thisLine, startX() + p.x(), !view()->wrapCursor()));
2591 }
2592 
2593 // BEGIN EVENT HANDLING STUFF
2594 
eventFilter(QObject * obj,QEvent * e)2595 bool KateViewInternal::eventFilter(QObject *obj, QEvent *e)
2596 {
2597     switch (e->type()) {
2598     case QEvent::ChildAdded:
2599     case QEvent::ChildRemoved: {
2600         QChildEvent *c = static_cast<QChildEvent *>(e);
2601         if (c->added()) {
2602             c->child()->installEventFilter(this);
2603 
2604         } else if (c->removed()) {
2605             c->child()->removeEventFilter(this);
2606         }
2607     } break;
2608 
2609     case QEvent::ShortcutOverride: {
2610         QKeyEvent *k = static_cast<QKeyEvent *>(e);
2611 
2612         if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) {
2613             if (view()->isCompletionActive()) {
2614                 view()->abortCompletion();
2615                 k->accept();
2616                 // qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "aborting completion";
2617                 return true;
2618             } else if (!view()->bottomViewBar()->hiddenOrPermanent()) {
2619                 view()->bottomViewBar()->hideCurrentBarWidget();
2620                 k->accept();
2621                 // qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "closing view bar";
2622                 return true;
2623             } else if (!view()->config()->persistentSelection() && view()->selection()) {
2624                 m_currentInputMode->clearSelection();
2625                 k->accept();
2626                 // qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "clearing selection";
2627                 return true;
2628             }
2629         }
2630 
2631         if (m_currentInputMode->stealKey(k)) {
2632             k->accept();
2633             return true;
2634         }
2635 
2636         // CompletionReplayer.replay only gets called when a Ctrl-Space gets to InsertViMode::handleKeyPress
2637         // Workaround for BUG: 334032 (https://bugs.kde.org/show_bug.cgi?id=334032)
2638         if (k->key() == Qt::Key_Space && k->modifiers() == Qt::ControlModifier) {
2639             keyPressEvent(k);
2640             if (k->isAccepted()) {
2641                 return true;
2642             }
2643         }
2644 
2645     } break;
2646 
2647     case QEvent::KeyPress: {
2648         QKeyEvent *k = static_cast<QKeyEvent *>(e);
2649 
2650         // Override all other single key shortcuts which do not use a modifier other than Shift
2651         if (obj == this && (!k->modifiers() || k->modifiers() == Qt::ShiftModifier)) {
2652             keyPressEvent(k);
2653             if (k->isAccepted()) {
2654                 // qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "using keystroke";
2655                 return true;
2656             }
2657         }
2658 
2659         // qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "ignoring";
2660     } break;
2661 
2662     case QEvent::DragMove: {
2663         QPoint currentPoint = ((QDragMoveEvent *)e)->pos();
2664 
2665         QRect doNotScrollRegion(s_scrollMargin, s_scrollMargin, width() - s_scrollMargin * 2, height() - s_scrollMargin * 2);
2666 
2667         if (!doNotScrollRegion.contains(currentPoint)) {
2668             startDragScroll();
2669             // Keep sending move events
2670             ((QDragMoveEvent *)e)->accept(QRect(0, 0, 0, 0));
2671         }
2672 
2673         dragMoveEvent((QDragMoveEvent *)e);
2674     } break;
2675 
2676     case QEvent::DragLeave:
2677         // happens only when pressing ESC while dragging
2678         stopDragScroll();
2679         break;
2680 
2681     case QEvent::WindowDeactivate:
2682         hideBracketMatchPreview();
2683         break;
2684 
2685     case QEvent::ScrollPrepare: {
2686         QScrollPrepareEvent *s = static_cast<QScrollPrepareEvent *>(e);
2687         scrollPrepareEvent(s);
2688     } return true;
2689 
2690     case QEvent::Scroll: {
2691         QScrollEvent *s = static_cast<QScrollEvent *>(e);
2692         scrollEvent(s);
2693     } return true;
2694 
2695     default:
2696         break;
2697     }
2698 
2699     return QWidget::eventFilter(obj, e);
2700 }
2701 
keyPressEvent(QKeyEvent * e)2702 void KateViewInternal::keyPressEvent(QKeyEvent *e)
2703 {
2704     m_shiftKeyPressed = e->modifiers() & Qt::ShiftModifier;
2705     if (e->key() == Qt::Key_Left && e->modifiers() == Qt::AltModifier) {
2706         view()->emitNavigateLeft();
2707         e->setAccepted(true);
2708         return;
2709     }
2710     if (e->key() == Qt::Key_Right && e->modifiers() == Qt::AltModifier) {
2711         view()->emitNavigateRight();
2712         e->setAccepted(true);
2713         return;
2714     }
2715     if (e->key() == Qt::Key_Up && e->modifiers() == Qt::AltModifier) {
2716         view()->emitNavigateUp();
2717         e->setAccepted(true);
2718         return;
2719     }
2720     if (e->key() == Qt::Key_Down && e->modifiers() == Qt::AltModifier) {
2721         view()->emitNavigateDown();
2722         e->setAccepted(true);
2723         return;
2724     }
2725     if (e->key() == Qt::Key_Return && e->modifiers() == Qt::AltModifier) {
2726         view()->emitNavigateAccept();
2727         e->setAccepted(true);
2728         return;
2729     }
2730     if (e->key() == Qt::Key_Backspace && e->modifiers() == Qt::AltModifier) {
2731         view()->emitNavigateBack();
2732         e->setAccepted(true);
2733         return;
2734     }
2735 
2736     if (e->key() == Qt::Key_Alt && view()->completionWidget()->isCompletionActive()) {
2737         m_completionItemExpanded = view()->completionWidget()->toggleExpanded(true);
2738         view()->completionWidget()->resetHadNavigation();
2739         m_altDownTime.start();
2740     }
2741 
2742     // Note: AND'ing with <Shift> is a quick hack to fix Key_Enter
2743     const int key = e->key() | (e->modifiers() & Qt::ShiftModifier);
2744 
2745     if (m_currentInputMode->keyPress(e)) {
2746         return;
2747     }
2748 
2749     if (!doc()->isReadWrite()) {
2750         e->ignore();
2751         return;
2752     }
2753 
2754     if ((key == Qt::Key_Return) || (key == Qt::Key_Enter) || (key == Qt::SHIFT + Qt::Key_Return) || (key == Qt::SHIFT + Qt::Key_Enter)) {
2755         view()->keyReturn();
2756         e->accept();
2757         return;
2758     }
2759 
2760     if (key == Qt::Key_Backspace || key == Qt::SHIFT + Qt::Key_Backspace) {
2761         // view()->backspace();
2762         e->accept();
2763 
2764         return;
2765     }
2766 
2767     if (key == Qt::Key_Tab || key == Qt::SHIFT + Qt::Key_Backtab || key == Qt::Key_Backtab) {
2768         if (view()->completionWidget()->isCompletionActive()) {
2769             e->accept();
2770             view()->completionWidget()->tab(key != Qt::Key_Tab);
2771             return;
2772         }
2773 
2774         if (key == Qt::Key_Tab) {
2775             uint tabHandling = doc()->config()->tabHandling();
2776             // convert tabSmart into tabInsertsTab or tabIndents:
2777             if (tabHandling == KateDocumentConfig::tabSmart) {
2778                 // multiple lines selected
2779                 if (view()->selection() && !view()->selectionRange().onSingleLine()) {
2780                     tabHandling = KateDocumentConfig::tabIndents;
2781                 }
2782 
2783                 // otherwise: take look at cursor position
2784                 else {
2785                     // if the cursor is at or before the first non-space character
2786                     // or on an empty line,
2787                     // Tab indents, otherwise it inserts a tab character.
2788                     Kate::TextLine line = doc()->kateTextLine(m_cursor.line());
2789                     int first = line->firstChar();
2790                     if (first < 0 || m_cursor.column() <= first) {
2791                         tabHandling = KateDocumentConfig::tabIndents;
2792                     } else {
2793                         tabHandling = KateDocumentConfig::tabInsertsTab;
2794                     }
2795                 }
2796             }
2797 
2798             // either we just insert a tab or we convert that into an indent action
2799             if (tabHandling == KateDocumentConfig::tabInsertsTab) {
2800                 doc()->typeChars(m_view, QStringLiteral("\t"));
2801             } else {
2802                 doc()->indent(view()->selection() ? view()->selectionRange() : KTextEditor::Range(m_cursor.line(), 0, m_cursor.line(), 0), 1);
2803             }
2804 
2805             e->accept();
2806 
2807             return;
2808         } else if (doc()->config()->tabHandling() != KateDocumentConfig::tabInsertsTab) {
2809             // key == Qt::SHIFT+Qt::Key_Backtab || key == Qt::Key_Backtab
2810             doc()->indent(view()->selection() ? view()->selectionRange() : KTextEditor::Range(m_cursor.line(), 0, m_cursor.line(), 0), -1);
2811             e->accept();
2812 
2813             return;
2814         }
2815     }
2816 
2817     if (isAcceptableInput(e)) {
2818         doc()->typeChars(m_view, e->text());
2819         e->accept();
2820         return;
2821     }
2822 
2823     e->ignore();
2824 }
2825 
keyReleaseEvent(QKeyEvent * e)2826 void KateViewInternal::keyReleaseEvent(QKeyEvent *e)
2827 {
2828     if (e->key() == Qt::Key_Alt && view()->completionWidget()->isCompletionActive()
2829         && ((m_completionItemExpanded && (view()->completionWidget()->hadNavigation() || m_altDownTime.elapsed() > 300))
2830             || (!m_completionItemExpanded && !view()->completionWidget()->hadNavigation()))) {
2831         view()->completionWidget()->toggleExpanded(false, true);
2832     }
2833 
2834     if (m_shiftKeyPressed && (e->modifiers() & Qt::ShiftModifier) == 0) {
2835         m_shiftKeyPressed = false;
2836 
2837         if (m_selChangedByUser) {
2838             if (view()->selection()) {
2839                 QApplication::clipboard()->setText(view()->selectionText(), QClipboard::Selection);
2840             }
2841 
2842             m_selChangedByUser = false;
2843         }
2844     }
2845 
2846     e->ignore();
2847     return;
2848 }
2849 
isAcceptableInput(const QKeyEvent * e) const2850 bool KateViewInternal::isAcceptableInput(const QKeyEvent *e) const
2851 {
2852     // reimplemented from QInputControl::isAcceptableInput()
2853 
2854     const QString text = e->text();
2855     if (text.isEmpty()) {
2856         return false;
2857     }
2858 
2859     const QChar c = text.at(0);
2860 
2861     // Formatting characters such as ZWNJ, ZWJ, RLM, etc. This needs to go before the
2862     // next test, since CTRL+SHIFT is sometimes used to input it on Windows.
2863     // see bug 389796 (typing formatting characters such as ZWNJ)
2864     // and bug 396764 (typing soft-hyphens)
2865     if (c.category() == QChar::Other_Format) {
2866         return true;
2867     }
2868 
2869     // QTBUG-35734: ignore Ctrl/Ctrl+Shift; accept only AltGr (Alt+Ctrl) on German keyboards
2870     if ((e->modifiers() == Qt::ControlModifier) || (e->modifiers() == (Qt::ShiftModifier | Qt::ControlModifier))) {
2871         return false;
2872     }
2873 
2874     // printable or private use is good, see e.g. bug 366424 (typing "private use" unicode characters)
2875     return c.isPrint() || (c.category() == QChar::Other_PrivateUse);
2876 }
2877 
contextMenuEvent(QContextMenuEvent * e)2878 void KateViewInternal::contextMenuEvent(QContextMenuEvent *e)
2879 {
2880     // calculate where to show the context menu
2881 
2882     QPoint p = e->pos();
2883 
2884     if (e->reason() == QContextMenuEvent::Keyboard) {
2885         makeVisible(m_displayCursor, 0);
2886         p = cursorCoordinates(false);
2887         p.rx() -= startX();
2888     } else if (!view()->selection() || view()->config()->persistentSelection()) {
2889         placeCursor(e->pos());
2890     }
2891 
2892     // show it
2893     if (view()->contextMenu()) {
2894         view()->spellingMenu()->setUseMouseForMisspelledRange((e->reason() == QContextMenuEvent::Mouse));
2895         view()->contextMenu()->popup(mapToGlobal(p));
2896         e->accept();
2897     }
2898 }
2899 
mousePressEvent(QMouseEvent * e)2900 void KateViewInternal::mousePressEvent(QMouseEvent *e)
2901 {
2902     // was an inline note clicked?
2903     const auto noteData = inlineNoteAt(e->globalPos());
2904     const KTextEditor::InlineNote note(noteData);
2905     if (note.position().isValid()) {
2906         note.provider()->inlineNoteActivated(noteData, e->button(), e->globalPos());
2907         return;
2908     }
2909 
2910     // no -- continue with normal handling
2911     switch (e->button()) {
2912     case Qt::LeftButton:
2913 
2914         m_selChangedByUser = false;
2915 
2916         if (m_possibleTripleClick) {
2917             m_possibleTripleClick = false;
2918 
2919             m_selectionMode = Line;
2920 
2921             if (e->modifiers() & Qt::ShiftModifier) {
2922                 updateSelection(m_cursor, true);
2923             } else {
2924                 view()->selectLine(m_cursor);
2925                 if (view()->selection()) {
2926                     m_selectAnchor = view()->selectionRange().start();
2927                 }
2928             }
2929 
2930             if (view()->selection()) {
2931                 QApplication::clipboard()->setText(view()->selectionText(), QClipboard::Selection);
2932             }
2933 
2934             // Keep the line at the select anchor selected during further
2935             // mouse selection
2936             if (m_selectAnchor.line() > view()->selectionRange().start().line()) {
2937                 // Preserve the last selected line
2938                 if (m_selectAnchor == view()->selectionRange().end() && m_selectAnchor.column() == 0) {
2939                     m_selectionCached.setStart(KTextEditor::Cursor(m_selectAnchor.line() - 1, 0));
2940                 } else {
2941                     m_selectionCached.setStart(KTextEditor::Cursor(m_selectAnchor.line(), 0));
2942                 }
2943                 m_selectionCached.setEnd(view()->selectionRange().end());
2944             } else {
2945                 // Preserve the first selected line
2946                 m_selectionCached.setStart(view()->selectionRange().start());
2947                 if (view()->selectionRange().end().line() > view()->selectionRange().start().line()) {
2948                     m_selectionCached.setEnd(KTextEditor::Cursor(view()->selectionRange().start().line() + 1, 0));
2949                 } else {
2950                     m_selectionCached.setEnd(view()->selectionRange().end());
2951                 }
2952             }
2953 
2954             moveCursorToSelectionEdge();
2955 
2956             m_scrollX = 0;
2957             m_scrollY = 0;
2958             m_scrollTimer.start(50);
2959 
2960             e->accept();
2961             return;
2962         } else if (m_selectionMode == Default) {
2963             m_selectionMode = Mouse;
2964         }
2965 
2966         // request the software keyboard, if any
2967         if (e->button() == Qt::LeftButton && qApp->autoSipEnabled()) {
2968             QStyle::RequestSoftwareInputPanel behavior = QStyle::RequestSoftwareInputPanel(style()->styleHint(QStyle::SH_RequestSoftwareInputPanel));
2969             if (hasFocus() || behavior == QStyle::RSIP_OnMouseClick) {
2970                 QEvent event(QEvent::RequestSoftwareInputPanel);
2971                 QApplication::sendEvent(this, &event);
2972             }
2973         }
2974 
2975         if (e->modifiers() & Qt::ShiftModifier) {
2976             if (!m_selectAnchor.isValid()) {
2977                 m_selectAnchor = m_cursor;
2978             }
2979         } else {
2980             m_selectionCached = KTextEditor::Range::invalid();
2981         }
2982 
2983         if (view()->config()->textDragAndDrop() && !(e->modifiers() & Qt::ShiftModifier) && isTargetSelected(e->pos())) {
2984             m_dragInfo.state = diPending;
2985             m_dragInfo.start = e->pos();
2986         } else {
2987             m_dragInfo.state = diNone;
2988 
2989             if (e->modifiers() & Qt::ShiftModifier) {
2990                 placeCursor(e->pos(), true, false);
2991                 if (m_selectionCached.start().isValid()) {
2992                     if (m_cursor.toCursor() < m_selectionCached.start()) {
2993                         m_selectAnchor = m_selectionCached.end();
2994                     } else {
2995                         m_selectAnchor = m_selectionCached.start();
2996                     }
2997                 }
2998                 setSelection(KTextEditor::Range(m_selectAnchor, m_cursor));
2999             } else {
3000                 placeCursor(e->pos());
3001             }
3002 
3003             m_scrollX = 0;
3004             m_scrollY = 0;
3005 
3006             m_scrollTimer.start(50);
3007         }
3008 
3009         e->accept();
3010         break;
3011 
3012     case Qt::RightButton:
3013         if (e->pos().x() == 0) {
3014             // Special handling for folding by right click
3015             placeCursor(e->pos());
3016             e->accept();
3017         }
3018         break;
3019 
3020     default:
3021         e->ignore();
3022         break;
3023     }
3024 }
3025 
mouseDoubleClickEvent(QMouseEvent * e)3026 void KateViewInternal::mouseDoubleClickEvent(QMouseEvent *e)
3027 {
3028     if (e->button() == Qt::LeftButton) {
3029         m_selectionMode = Word;
3030 
3031         if (e->modifiers() & Qt::ShiftModifier) {
3032             // Now select the word under the select anchor
3033             int cs;
3034             int ce;
3035             Kate::TextLine l = doc()->kateTextLine(m_selectAnchor.line());
3036 
3037             ce = m_selectAnchor.column();
3038             if (ce > 0 && doc()->highlight()->isInWord(l->at(ce))) {
3039                 for (; ce < l->length(); ce++) {
3040                     if (!doc()->highlight()->isInWord(l->at(ce))) {
3041                         break;
3042                     }
3043                 }
3044             }
3045 
3046             cs = m_selectAnchor.column() - 1;
3047             if (cs < doc()->lineLength(m_selectAnchor.line()) && doc()->highlight()->isInWord(l->at(cs))) {
3048                 for (cs--; cs >= 0; cs--) {
3049                     if (!doc()->highlight()->isInWord(l->at(cs))) {
3050                         break;
3051                     }
3052                 }
3053             }
3054 
3055             // ...and keep it selected
3056             if (cs + 1 < ce) {
3057                 m_selectionCached.setStart(KTextEditor::Cursor(m_selectAnchor.line(), cs + 1));
3058                 m_selectionCached.setEnd(KTextEditor::Cursor(m_selectAnchor.line(), ce));
3059             } else {
3060                 m_selectionCached.setStart(m_selectAnchor);
3061                 m_selectionCached.setEnd(m_selectAnchor);
3062             }
3063             // Now word select to the mouse cursor
3064             placeCursor(e->pos(), true);
3065         } else {
3066             // first clear the selection, otherwise we run into bug #106402
3067             // ...and set the cursor position, for the same reason (otherwise there
3068             // are *other* idiosyncrasies we can't fix without reintroducing said
3069             // bug)
3070             // Parameters: don't redraw, and don't emit selectionChanged signal yet
3071             view()->clearSelection(false, false);
3072             placeCursor(e->pos());
3073             view()->selectWord(m_cursor);
3074             cursorToMatchingBracket(true);
3075 
3076             if (view()->selection()) {
3077                 m_selectAnchor = view()->selectionRange().start();
3078                 m_selectionCached = view()->selectionRange();
3079             } else {
3080                 m_selectAnchor = m_cursor;
3081                 m_selectionCached = KTextEditor::Range(m_cursor, m_cursor);
3082             }
3083         }
3084 
3085         // Move cursor to end (or beginning) of selected word
3086 #ifndef Q_OS_MACOS
3087         if (view()->selection()) {
3088             QApplication::clipboard()->setText(view()->selectionText(), QClipboard::Selection);
3089         }
3090 #endif
3091 
3092         moveCursorToSelectionEdge();
3093         m_possibleTripleClick = true;
3094         QTimer::singleShot(QApplication::doubleClickInterval(), this, SLOT(tripleClickTimeout()));
3095 
3096         m_scrollX = 0;
3097         m_scrollY = 0;
3098 
3099         m_scrollTimer.start(50);
3100 
3101         e->accept();
3102     } else {
3103         e->ignore();
3104     }
3105 }
3106 
tripleClickTimeout()3107 void KateViewInternal::tripleClickTimeout()
3108 {
3109     m_possibleTripleClick = false;
3110 }
3111 
beginSelectLine(const QPoint & pos)3112 void KateViewInternal::beginSelectLine(const QPoint &pos)
3113 {
3114     placeCursor(pos);
3115     m_possibleTripleClick = true; // set so subsequent mousePressEvent will select line
3116 }
3117 
mouseReleaseEvent(QMouseEvent * e)3118 void KateViewInternal::mouseReleaseEvent(QMouseEvent *e)
3119 {
3120     switch (e->button()) {
3121     case Qt::LeftButton:
3122         m_selectionMode = Default;
3123         //       m_selectionCached.start().setLine( -1 );
3124 
3125         if (m_selChangedByUser) {
3126             if (view()->selection()) {
3127                 QApplication::clipboard()->setText(view()->selectionText(), QClipboard::Selection);
3128             }
3129             moveCursorToSelectionEdge();
3130 
3131             m_selChangedByUser = false;
3132         }
3133 
3134         if (m_dragInfo.state == diPending) {
3135             placeCursor(e->pos(), e->modifiers() & Qt::ShiftModifier);
3136         } else if (m_dragInfo.state == diNone) {
3137             m_scrollTimer.stop();
3138         }
3139 
3140         m_dragInfo.state = diNone;
3141 
3142         e->accept();
3143         break;
3144 
3145     case Qt::MiddleButton:
3146         if (!view()->config()->mousePasteAtCursorPosition()) {
3147             placeCursor(e->pos());
3148         }
3149 
3150         if (doc()->isReadWrite()) {
3151             QString clipboard = QApplication::clipboard()->text(QClipboard::Selection);
3152             view()->paste(&clipboard);
3153         }
3154 
3155         e->accept();
3156         break;
3157 
3158     default:
3159         e->ignore();
3160         break;
3161     }
3162 }
3163 
leaveEvent(QEvent *)3164 void KateViewInternal::leaveEvent(QEvent *)
3165 {
3166     m_textHintTimer.stop();
3167 
3168     // fix bug 194452, scrolling keeps going if you scroll via mouse drag and press and other mouse
3169     // button outside the view area
3170     if (m_dragInfo.state == diNone) {
3171         m_scrollTimer.stop();
3172     }
3173 
3174     hideBracketMatchPreview();
3175 }
3176 
coordinatesToCursor(const QPoint & _coord,bool includeBorder) const3177 KTextEditor::Cursor KateViewInternal::coordinatesToCursor(const QPoint &_coord, bool includeBorder) const
3178 {
3179     QPoint coord(_coord);
3180 
3181     KTextEditor::Cursor ret = KTextEditor::Cursor::invalid();
3182 
3183     if (includeBorder) {
3184         coord.rx() -= m_leftBorder->width();
3185     }
3186     coord.rx() += startX();
3187 
3188     const KateTextLayout &thisLine = yToKateTextLayout(coord.y());
3189     if (thisLine.isValid()) {
3190         ret = renderer()->xToCursor(thisLine, coord.x(), !view()->wrapCursor());
3191     }
3192 
3193     if (ret.column() > view()->document()->lineLength(ret.line())) {
3194         // The cursor is beyond the end of the line; in that case the renderer
3195         // gives the index of the character behind the last one.
3196         return KTextEditor::Cursor::invalid();
3197     }
3198 
3199     return ret;
3200 }
3201 
mouseMoveEvent(QMouseEvent * e)3202 void KateViewInternal::mouseMoveEvent(QMouseEvent *e)
3203 {
3204     if (m_scroller->state() != QScroller::Inactive) {
3205         // Touchscreen is handled by scrollEvent()
3206         return;
3207     }
3208     KTextEditor::Cursor newPosition = coordinatesToCursor(e->pos(), false);
3209     if (newPosition != m_mouse) {
3210         m_mouse = newPosition;
3211         mouseMoved();
3212     }
3213 
3214     if (e->buttons() == Qt::NoButton) {
3215         auto noteData = inlineNoteAt(e->globalPos());
3216         auto focusChanged = false;
3217         if (noteData.m_position.isValid()) {
3218             if (!m_activeInlineNote.m_position.isValid()) {
3219                 // no active note -- focus in
3220                 tagLine(noteData.m_position);
3221                 focusChanged = true;
3222                 noteData.m_underMouse = true;
3223                 noteData.m_provider->inlineNoteFocusInEvent(KTextEditor::InlineNote(noteData), e->globalPos());
3224                 m_activeInlineNote = noteData;
3225             } else {
3226                 noteData.m_provider->inlineNoteMouseMoveEvent(KTextEditor::InlineNote(noteData), e->globalPos());
3227             }
3228         } else if (m_activeInlineNote.m_position.isValid()) {
3229             tagLine(m_activeInlineNote.m_position);
3230             focusChanged = true;
3231             m_activeInlineNote.m_underMouse = false;
3232             m_activeInlineNote.m_provider->inlineNoteFocusOutEvent(KTextEditor::InlineNote(m_activeInlineNote));
3233             m_activeInlineNote = {};
3234         }
3235         if (focusChanged) {
3236             // the note might change its appearance in reaction to the focus event
3237             updateDirty();
3238         }
3239     }
3240 
3241     if (e->buttons() & Qt::LeftButton) {
3242         if (m_dragInfo.state == diPending) {
3243             // we had a mouse down, but haven't confirmed a drag yet
3244             // if the mouse has moved sufficiently, we will confirm
3245             QPoint p(e->pos() - m_dragInfo.start);
3246 
3247             // we've left the drag square, we can start a real drag operation now
3248             if (p.manhattanLength() > QApplication::startDragDistance()) {
3249                 doDrag();
3250             }
3251 
3252             return;
3253         } else if (m_dragInfo.state == diDragging) {
3254             // Don't do anything after a canceled drag until the user lets go of
3255             // the mouse button!
3256             return;
3257         }
3258 
3259         m_mouseX = e->x();
3260         m_mouseY = e->y();
3261 
3262         m_scrollX = 0;
3263         m_scrollY = 0;
3264         int d = renderer()->lineHeight();
3265 
3266         if (m_mouseX < 0) {
3267             m_scrollX = -d;
3268         }
3269 
3270         if (m_mouseX > width()) {
3271             m_scrollX = d;
3272         }
3273 
3274         if (m_mouseY < 0) {
3275             m_mouseY = 0;
3276             m_scrollY = -d;
3277         }
3278 
3279         if (m_mouseY > height()) {
3280             m_mouseY = height();
3281             m_scrollY = d;
3282         }
3283 
3284         if (!m_scrollY) {
3285             placeCursor(QPoint(m_mouseX, m_mouseY), true);
3286         }
3287 
3288     } else {
3289         if (view()->config()->textDragAndDrop() && isTargetSelected(e->pos())) {
3290             // mouse is over selected text. indicate that the text is draggable by setting
3291             // the arrow cursor as other Qt text editing widgets do
3292             if (m_mouseCursor != Qt::ArrowCursor) {
3293                 m_mouseCursor = Qt::ArrowCursor;
3294                 setCursor(m_mouseCursor);
3295             }
3296         } else {
3297             // normal text cursor
3298             if (m_mouseCursor != Qt::IBeamCursor) {
3299                 m_mouseCursor = Qt::IBeamCursor;
3300                 setCursor(m_mouseCursor);
3301             }
3302         }
3303         // We need to check whether the mouse position is actually within the widget,
3304         // because other widgets like the icon border forward their events to this,
3305         // and we will create invalid text hint requests if we don't check
3306         if (textHintsEnabled() && geometry().contains(parentWidget()->mapFromGlobal(e->globalPos()))) {
3307             if (QToolTip::isVisible()) {
3308                 QToolTip::hideText();
3309             }
3310             m_textHintTimer.start(m_textHintDelay);
3311             m_textHintPos = e->pos();
3312         }
3313     }
3314 }
3315 
updateDirty()3316 void KateViewInternal::updateDirty()
3317 {
3318     const int h = renderer()->lineHeight();
3319 
3320     int currentRectStart = -1;
3321     int currentRectEnd = -1;
3322 
3323     QRegion updateRegion;
3324 
3325     {
3326         for (int i = 0; i < cache()->viewCacheLineCount(); ++i) {
3327             if (cache()->viewLine(i).isDirty()) {
3328                 if (currentRectStart == -1) {
3329                     currentRectStart = h * i;
3330                     currentRectEnd = h;
3331                 } else {
3332                     currentRectEnd += h;
3333                 }
3334 
3335             } else if (currentRectStart != -1) {
3336                 updateRegion += QRect(0, currentRectStart, width(), currentRectEnd);
3337                 currentRectStart = -1;
3338                 currentRectEnd = -1;
3339             }
3340         }
3341     }
3342 
3343     if (currentRectStart != -1) {
3344         updateRegion += QRect(0, currentRectStart, width(), currentRectEnd);
3345     }
3346 
3347     if (!updateRegion.isEmpty()) {
3348         if (debugPainting) {
3349             qCDebug(LOG_KTE) << "Update dirty region " << updateRegion;
3350         }
3351         update(updateRegion);
3352     }
3353 }
3354 
hideEvent(QHideEvent * e)3355 void KateViewInternal::hideEvent(QHideEvent *e)
3356 {
3357     Q_UNUSED(e);
3358     if (view()->isCompletionActive()) {
3359         view()->completionWidget()->abortCompletion();
3360     }
3361 }
3362 
paintEvent(QPaintEvent * e)3363 void KateViewInternal::paintEvent(QPaintEvent *e)
3364 {
3365     if (debugPainting) {
3366         qCDebug(LOG_KTE) << "GOT PAINT EVENT: Region" << e->region();
3367     }
3368 
3369     const QRect &unionRect = e->rect();
3370 
3371     int xStart = startX() + unionRect.x();
3372     int xEnd = xStart + unionRect.width();
3373     uint h = renderer()->lineHeight();
3374     uint startz = (unionRect.y() / h);
3375     uint endz = startz + 1 + (unionRect.height() / h);
3376     uint lineRangesSize = cache()->viewCacheLineCount();
3377     const KTextEditor::Cursor pos = m_cursor;
3378 
3379     QPainter paint(this);
3380 
3381     // THIS IS ULTRA EVIL AND ADDS STRANGE RENDERING ARTIFACTS WITH SCALING!!!!
3382     // SEE BUG https://bugreports.qt.io/browse/QTBUG-66036
3383     // paint.setRenderHints(QPainter::TextAntialiasing);
3384 
3385     paint.save();
3386 
3387     renderer()->setCaretStyle(m_currentInputMode->caretStyle());
3388     renderer()->setShowTabs(doc()->config()->showTabs());
3389     renderer()->setShowSpaces(doc()->config()->showSpaces());
3390     renderer()->updateMarkerSize();
3391 
3392     // paint line by line
3393     // this includes parts that span areas without real lines
3394     // translate to first line to paint
3395     paint.translate(unionRect.x(), startz * h);
3396     for (uint z = startz; z <= endz; z++) {
3397         // paint regions without lines mapped to
3398         if ((z >= lineRangesSize) || (cache()->viewLine(z).line() == -1)) {
3399             if (!(z >= lineRangesSize)) {
3400                 cache()->viewLine(z).setDirty(false);
3401             }
3402             paint.fillRect(0, 0, unionRect.width(), h, renderer()->config()->backgroundColor());
3403         }
3404 
3405         // paint text lines
3406         else {
3407             // If viewLine() returns non-zero, then a document line was split
3408             // in several visual lines, and we're trying to paint visual line
3409             // that is not the first.  In that case, this line was already
3410             // painted previously, since KateRenderer::paintTextLine paints
3411             // all visual lines.
3412             //
3413             // Except if we're at the start of the region that needs to
3414             // be painted -- when no previous calls to paintTextLine were made.
3415             KateTextLayout &thisLine = cache()->viewLine(z);
3416             if (!thisLine.viewLine() || z == startz) {
3417                 // paint our line
3418                 // set clipping region to only paint the relevant parts
3419                 paint.save();
3420                 paint.translate(QPoint(0, h * -thisLine.viewLine()));
3421 
3422                 // compute rect for line, fill the stuff
3423                 // important: as we allow some ARGB colors for other stuff, it is REALLY important to fill the full range once!
3424                 const QRectF lineRect(0, 0, unionRect.width(), h * thisLine.kateLineLayout()->viewLineCount());
3425                 paint.fillRect(lineRect, renderer()->config()->backgroundColor());
3426 
3427                 // THIS IS ULTRA EVIL AND ADDS STRANGE RENDERING ARTIFACTS WITH SCALING!!!!
3428                 // SEE BUG https://bugreports.qt.io/browse/QTBUG-66036
3429                 // => using a QRectF solves the cut of 1 pixel, the same call with QRect does create artifacts!
3430                 paint.setClipRect(lineRect);
3431                 renderer()->paintTextLine(paint, thisLine.kateLineLayout(), xStart, xEnd, &pos);
3432                 paint.restore();
3433 
3434                 // line painted, reset and state + mark line as non-dirty
3435                 thisLine.setDirty(false);
3436             }
3437         }
3438 
3439         // translate to next line
3440         paint.translate(0, h);
3441     }
3442 
3443     paint.restore();
3444 
3445     if (m_textAnimation) {
3446         m_textAnimation->draw(paint);
3447     }
3448 }
3449 
resizeEvent(QResizeEvent * e)3450 void KateViewInternal::resizeEvent(QResizeEvent *e)
3451 {
3452     bool expandedHorizontally = width() > e->oldSize().width();
3453     bool expandedVertically = height() > e->oldSize().height();
3454     bool heightChanged = height() != e->oldSize().height();
3455 
3456     m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height());
3457     m_madeVisible = false;
3458 
3459     // resize the bracket match preview
3460     if (m_bmPreview) {
3461         showBracketMatchPreview();
3462     }
3463 
3464     if (heightChanged) {
3465         setAutoCenterLines(m_autoCenterLines, false);
3466         m_cachedMaxStartPos.setPosition(-1, -1);
3467     }
3468 
3469     if (view()->dynWordWrap()) {
3470         bool dirtied = false;
3471 
3472         for (int i = 0; i < cache()->viewCacheLineCount(); i++) {
3473             // find the first dirty line
3474             // the word wrap updateView algorithm is forced to check all lines after a dirty one
3475             KateTextLayout viewLine = cache()->viewLine(i);
3476 
3477             if (viewLine.wrap() || viewLine.isRightToLeft() || viewLine.width() > width()) {
3478                 dirtied = true;
3479                 viewLine.setDirty();
3480                 break;
3481             }
3482         }
3483 
3484         if (dirtied || heightChanged) {
3485             updateView(true);
3486             m_leftBorder->update();
3487         }
3488     } else {
3489         updateView();
3490 
3491         if (expandedHorizontally && startX() > 0) {
3492             scrollColumns(startX() - (width() - e->oldSize().width()));
3493         }
3494     }
3495 
3496     if (width() < e->oldSize().width() && !view()->wrapCursor()) {
3497         // May have to restrain cursor to new smaller width...
3498         if (m_cursor.column() > doc()->lineLength(m_cursor.line())) {
3499             KateTextLayout thisLine = currentLayout();
3500 
3501             KTextEditor::Cursor newCursor(m_cursor.line(),
3502                                           thisLine.endCol() + ((width() - thisLine.xOffset() - (thisLine.width() - startX())) / renderer()->spaceWidth()) - 1);
3503             if (newCursor.column() < m_cursor.column()) {
3504                 updateCursor(newCursor);
3505             }
3506         }
3507     }
3508 
3509     if (expandedVertically) {
3510         KTextEditor::Cursor max = maxStartPos();
3511         if (startPos() > max) {
3512             scrollPos(max);
3513             return; // already fired displayRangeChanged
3514         }
3515     }
3516     Q_EMIT view()->displayRangeChanged(m_view);
3517 }
3518 
moveEvent(QMoveEvent * e)3519 void KateViewInternal::moveEvent(QMoveEvent *e)
3520 {
3521     // move the bracket match preview to the new location
3522     if (e->pos() != e->oldPos() && m_bmPreview) {
3523         showBracketMatchPreview();
3524     }
3525 
3526     QWidget::moveEvent(e);
3527 }
3528 
scrollTimeout()3529 void KateViewInternal::scrollTimeout()
3530 {
3531     if (m_scrollX || m_scrollY) {
3532         const int scrollTo = startPos().line() + (m_scrollY / (int)renderer()->lineHeight());
3533         placeCursor(QPoint(m_mouseX, m_mouseY), true);
3534         scrollLines(scrollTo);
3535     }
3536 }
3537 
cursorTimeout()3538 void KateViewInternal::cursorTimeout()
3539 {
3540     if (!debugPainting && m_currentInputMode->blinkCaret()) {
3541         renderer()->setDrawCaret(!renderer()->drawCaret());
3542         paintCursor();
3543     }
3544 }
3545 
textHintTimeout()3546 void KateViewInternal::textHintTimeout()
3547 {
3548     m_textHintTimer.stop();
3549 
3550     KTextEditor::Cursor c = coordinatesToCursor(m_textHintPos, false);
3551     if (!c.isValid()) {
3552         return;
3553     }
3554 
3555     QStringList textHints;
3556     for (KTextEditor::TextHintProvider *const p : m_textHintProviders) {
3557         if (!p) {
3558             continue;
3559         }
3560 
3561         const QString hint = p->textHint(m_view, c);
3562         if (!hint.isEmpty()) {
3563             textHints.append(hint);
3564         }
3565     }
3566 
3567     if (!textHints.isEmpty()) {
3568         qCDebug(LOG_KTE) << "Hint text: " << textHints;
3569         QString hint;
3570         for (const QString &str : std::as_const(textHints)) {
3571             hint += QStringLiteral("<p>%1</p>").arg(str);
3572         }
3573         QPoint pos(startX() + m_textHintPos.x(), m_textHintPos.y());
3574         QToolTip::showText(mapToGlobal(pos), hint);
3575     }
3576 }
3577 
focusInEvent(QFocusEvent *)3578 void KateViewInternal::focusInEvent(QFocusEvent *)
3579 {
3580     if (QApplication::cursorFlashTime() > 0) {
3581         m_cursorTimer.start(QApplication::cursorFlashTime() / 2);
3582     }
3583 
3584     paintCursor();
3585 
3586     doc()->setActiveView(m_view);
3587 
3588     // this will handle focus stuff in kateview
3589     view()->slotGotFocus();
3590 }
3591 
focusOutEvent(QFocusEvent *)3592 void KateViewInternal::focusOutEvent(QFocusEvent *)
3593 {
3594     // if (view()->isCompletionActive())
3595     // view()->abortCompletion();
3596 
3597     m_cursorTimer.stop();
3598     view()->renderer()->setDrawCaret(true);
3599     paintCursor();
3600 
3601     m_textHintTimer.stop();
3602 
3603     view()->slotLostFocus();
3604 
3605     hideBracketMatchPreview();
3606 }
3607 
doDrag()3608 void KateViewInternal::doDrag()
3609 {
3610     m_dragInfo.state = diDragging;
3611     m_dragInfo.dragObject = new QDrag(this);
3612     std::unique_ptr<QMimeData> mimeData(new QMimeData());
3613     mimeData->setText(view()->selectionText());
3614 
3615     const auto startCur = view()->selectionRange().start();
3616     const auto endCur = view()->selectionRange().end();
3617     if (!startCur.isValid() || !endCur.isValid()) {
3618         return;
3619     }
3620 
3621     int startLine = startCur.line();
3622     int endLine = endCur.line();
3623 
3624     /**
3625      * Get real first and last visible line nos.
3626      * This is important as startLine() / endLine() are virtual and we can't use
3627      * them here
3628      */
3629     const int firstVisibleLine = view()->firstDisplayedLineInternal(KTextEditor::View::RealLine);
3630     const int lastVisibleLine = view()->lastDisplayedLineInternal(KTextEditor::View::RealLine);
3631 
3632     // get visible selected lines
3633     for (int l = startLine; l <= endLine; ++l) {
3634         if (l >= firstVisibleLine) {
3635             break;
3636         }
3637         ++startLine;
3638     }
3639     for (int l = endLine; l >= startLine; --l) {
3640         if (l <= lastVisibleLine) {
3641             break;
3642         }
3643         --endLine;
3644     }
3645 
3646     // calculate the height / width / scale
3647     int w = 0;
3648     int h = 0;
3649     const QFontMetricsF &fm = renderer()->currentFontMetrics();
3650     for (int l = startLine; l <= endLine; ++l) {
3651         w = std::max((int)fm.horizontalAdvance(doc()->line(l)), w);
3652         h += fm.height();
3653     }
3654     qreal scale = h > m_view->height() / 2 ? 0.75 : 1.0;
3655 
3656     // Calculate start x pos on start line
3657     int sX = 0;
3658     if (startLine == startCur.line()) {
3659         sX = renderer()->cursorToX(cache()->textLayout(startCur), startCur, !view()->wrapCursor());
3660     }
3661 
3662     // Calculate end x pos on end line
3663     int eX = 0;
3664     if (endLine == endCur.line()) {
3665         eX = renderer()->cursorToX(cache()->textLayout(endCur), endCur, !view()->wrapCursor());
3666     }
3667 
3668     // Create a pixmap this selection
3669     const qreal dpr = devicePixelRatioF();
3670     QPixmap pixmap(w * dpr, h * dpr);
3671     if (!pixmap.isNull()) {
3672         pixmap.setDevicePixelRatio(dpr);
3673         pixmap.fill(Qt::transparent);
3674         renderer()->paintSelection(&pixmap, startLine, sX, endLine, eX, scale);
3675     }
3676 
3677     // Calculate position where pixmap will appear when user
3678     // starts dragging
3679     const int x = 0;
3680     /**
3681      * lineToVisibleLine() = real line => virtual line
3682      * This is necessary here because if there is a folding in the current
3683      * view lines, the y pos can be incorrect. So, we make sure to convert
3684      * it to virtual line before calculating y
3685      */
3686     const int y = lineToY(view()->m_textFolding.lineToVisibleLine(startLine));
3687     const QPoint pos = mapFromGlobal(QCursor::pos()) - QPoint(x, y);
3688 
3689     m_dragInfo.dragObject->setPixmap(pixmap);
3690     m_dragInfo.dragObject->setHotSpot(pos);
3691     m_dragInfo.dragObject->setMimeData(mimeData.release());
3692     m_dragInfo.dragObject->exec(Qt::MoveAction | Qt::CopyAction);
3693 }
3694 
dragEnterEvent(QDragEnterEvent * event)3695 void KateViewInternal::dragEnterEvent(QDragEnterEvent *event)
3696 {
3697     if (event->source() == this) {
3698         event->setDropAction(Qt::MoveAction);
3699     }
3700     event->setAccepted((event->mimeData()->hasText() && doc()->isReadWrite()) || event->mimeData()->hasUrls());
3701 }
3702 
fixDropEvent(QDropEvent * event)3703 void KateViewInternal::fixDropEvent(QDropEvent *event)
3704 {
3705     if (event->source() != this) {
3706         event->setDropAction(Qt::CopyAction);
3707     } else {
3708         Qt::DropAction action = Qt::MoveAction;
3709 #ifdef Q_WS_MAC
3710         if (event->keyboardModifiers() & Qt::AltModifier) {
3711             action = Qt::CopyAction;
3712         }
3713 #else
3714         if (event->keyboardModifiers() & Qt::ControlModifier) {
3715             action = Qt::CopyAction;
3716         }
3717 #endif
3718         event->setDropAction(action);
3719     }
3720 }
3721 
dragMoveEvent(QDragMoveEvent * event)3722 void KateViewInternal::dragMoveEvent(QDragMoveEvent *event)
3723 {
3724     // track the cursor to the current drop location
3725     placeCursor(event->pos(), true, false);
3726 
3727     // important: accept action to switch between copy and move mode
3728     // without this, the text will always be copied.
3729     fixDropEvent(event);
3730 }
3731 
dropEvent(QDropEvent * event)3732 void KateViewInternal::dropEvent(QDropEvent *event)
3733 {
3734     // if we have urls, pass this event off to the hosting application
3735     if (event->mimeData()->hasUrls()) {
3736         Q_EMIT dropEventPass(event);
3737         return;
3738     }
3739 
3740     if (event->mimeData()->hasText() && doc()->isReadWrite()) {
3741         const QString text = event->mimeData()->text();
3742         const bool blockMode = view()->blockSelection();
3743 
3744         fixDropEvent(event);
3745 
3746         // Remember where to paste/move...
3747         KTextEditor::Cursor targetCursor(m_cursor);
3748         // Use powerful MovingCursor to track our changes we may do
3749         std::unique_ptr<KTextEditor::MovingCursor> targetCursor2(doc()->newMovingCursor(m_cursor));
3750 
3751         // As always need the BlockMode some special treatment
3752         const KTextEditor::Range selRange(view()->selectionRange());
3753         const KTextEditor::Cursor blockAdjust(selRange.numberOfLines(), selRange.columnWidth());
3754 
3755         // Restore the cursor position before editStart(), so that it is correctly stored for the undo action
3756         if (event->dropAction() != Qt::CopyAction) {
3757             editSetCursor(selRange.end());
3758         } else {
3759             view()->clearSelection();
3760         }
3761 
3762         // use one transaction
3763         doc()->editStart();
3764 
3765         if (event->dropAction() != Qt::CopyAction) {
3766             view()->removeSelectedText();
3767             if (targetCursor2->toCursor() != targetCursor) {
3768                 // Hm, multi line selection moved down, we need to adjust our dumb cursor
3769                 targetCursor = targetCursor2->toCursor();
3770             }
3771             doc()->insertText(targetCursor2->toCursor(), text, blockMode);
3772 
3773         } else {
3774             doc()->insertText(targetCursor, text, blockMode);
3775         }
3776 
3777         if (blockMode) {
3778             setSelection(KTextEditor::Range(targetCursor, targetCursor + blockAdjust));
3779             editSetCursor(targetCursor + blockAdjust);
3780         } else {
3781             setSelection(KTextEditor::Range(targetCursor, targetCursor2->toCursor()));
3782             editSetCursor(targetCursor2->toCursor()); // Just to satisfy autotest
3783         }
3784 
3785         doc()->editEnd();
3786 
3787         event->acceptProposedAction();
3788         updateView();
3789     }
3790 
3791     // finally finish drag and drop mode
3792     m_dragInfo.state = diNone;
3793     // important, because the eventFilter`s DragLeave does not occur
3794     stopDragScroll();
3795 }
3796 // END EVENT HANDLING STUFF
3797 
clear()3798 void KateViewInternal::clear()
3799 {
3800     m_startPos.setPosition(0, 0);
3801     m_displayCursor = KTextEditor::Cursor(0, 0);
3802     m_cursor.setPosition(0, 0);
3803     cache()->clear();
3804     updateView(true);
3805     m_lineScroll->updatePixmap();
3806 }
3807 
wheelEvent(QWheelEvent * e)3808 void KateViewInternal::wheelEvent(QWheelEvent *e)
3809 {
3810     // check if this event should change the font size (Ctrl pressed, angle reported and not accidentally so)
3811     // Note: if detectZoomingEvent() doesn't unset the ControlModifier we'll get accelerated scrolling.
3812     if (m_zoomEventFilter->detectZoomingEvent(e)) {
3813         if (e->angleDelta().y() > 0) {
3814             slotIncFontSizes(qreal(e->angleDelta().y()) / QWheelEvent::DefaultDeltasPerStep);
3815         } else if (e->angleDelta().y() < 0) {
3816             slotDecFontSizes(qreal(-e->angleDelta().y()) / QWheelEvent::DefaultDeltasPerStep);
3817         }
3818 
3819         // accept always and be done for zooming
3820         e->accept();
3821         return;
3822     }
3823 
3824     // handle vertical scrolling via the scrollbar
3825     if (e->angleDelta().y() != 0) {
3826         // compute distance
3827         auto sign = m_lineScroll->invertedControls() ? -1 : 1;
3828         auto offset = sign * qreal(e->angleDelta().y()) / 120.0;
3829         if (e->modifiers() & Qt::ShiftModifier) {
3830             const auto pageStep = m_lineScroll->pageStep();
3831             offset = qBound(-pageStep, int(offset * pageStep), pageStep);
3832         } else {
3833             offset *= QApplication::wheelScrollLines();
3834         }
3835 
3836         // handle accumulation
3837         m_accumulatedScroll += offset - int(offset);
3838         const auto extraAccumulated = int(m_accumulatedScroll);
3839         m_accumulatedScroll -= extraAccumulated;
3840 
3841         // do scroll
3842         scrollViewLines(int(offset) + extraAccumulated);
3843         e->accept();
3844     }
3845 
3846     // handle horizontal scrolling via the scrollbar
3847     if (e->angleDelta().x() != 0) {
3848         // if we have dyn word wrap, we should ignore the scroll events
3849         if (view()->dynWordWrap()) {
3850             e->accept();
3851             return;
3852         }
3853 
3854         // if we scroll up/down we do not want to trigger unintended sideways scrolls
3855         if (qAbs(e->angleDelta().y()) > qAbs(e->angleDelta().x())) {
3856             e->accept();
3857             return;
3858         }
3859 
3860         QWheelEvent copy = *e;
3861         QApplication::sendEvent(m_columnScroll, &copy);
3862         if (copy.isAccepted()) {
3863             e->accept();
3864         }
3865     }
3866 
3867     // hide bracket match preview so that it won't linger while scrolling'
3868     hideBracketMatchPreview();
3869 }
3870 
scrollPrepareEvent(QScrollPrepareEvent * event)3871 void KateViewInternal::scrollPrepareEvent(QScrollPrepareEvent *event)
3872 {
3873     int lineHeight = renderer()->lineHeight();
3874     event->setViewportSize(QSizeF(0.0, 0.0));
3875     event->setContentPosRange(QRectF(0.0, 0.0, 0.0, m_lineScroll->maximum() * lineHeight));
3876     event->setContentPos(QPointF(0.0, m_lineScroll->value() * lineHeight));
3877     event->accept();
3878 }
3879 
scrollEvent(QScrollEvent * event)3880 void KateViewInternal::scrollEvent(QScrollEvent *event)
3881 {
3882     // FIXME Add horizontal scrolling, overscroll, scroll between lines, and word wrap awareness
3883     KTextEditor::Cursor newPos((int) event->contentPos().y() / renderer()->lineHeight(), 0);
3884     scrollPos(newPos);
3885     event->accept();
3886 }
3887 
startDragScroll()3888 void KateViewInternal::startDragScroll()
3889 {
3890     if (!m_dragScrollTimer.isActive()) {
3891         m_dragScrollTimer.start(s_scrollTime);
3892     }
3893 }
3894 
stopDragScroll()3895 void KateViewInternal::stopDragScroll()
3896 {
3897     m_dragScrollTimer.stop();
3898     updateView();
3899 }
3900 
doDragScroll()3901 void KateViewInternal::doDragScroll()
3902 {
3903     QPoint p = this->mapFromGlobal(QCursor::pos());
3904 
3905     int dx = 0;
3906     int dy = 0;
3907     if (p.y() < s_scrollMargin) {
3908         dy = p.y() - s_scrollMargin;
3909     } else if (p.y() > height() - s_scrollMargin) {
3910         dy = s_scrollMargin - (height() - p.y());
3911     }
3912 
3913     if (p.x() < s_scrollMargin) {
3914         dx = p.x() - s_scrollMargin;
3915     } else if (p.x() > width() - s_scrollMargin) {
3916         dx = s_scrollMargin - (width() - p.x());
3917     }
3918 
3919     dy /= 4;
3920 
3921     if (dy) {
3922         scrollLines(startLine() + dy);
3923     }
3924 
3925     if (columnScrollingPossible() && dx) {
3926         scrollColumns(qMin(startX() + dx, m_columnScroll->maximum()));
3927     }
3928 
3929     if (!dy && !dx) {
3930         stopDragScroll();
3931     }
3932 }
3933 
registerTextHintProvider(KTextEditor::TextHintProvider * provider)3934 void KateViewInternal::registerTextHintProvider(KTextEditor::TextHintProvider *provider)
3935 {
3936     if (std::find(m_textHintProviders.cbegin(), m_textHintProviders.cend(), provider) == m_textHintProviders.cend()) {
3937         m_textHintProviders.push_back(provider);
3938     }
3939 
3940     // we have a client, so start timeout
3941     m_textHintTimer.start(m_textHintDelay);
3942 }
3943 
unregisterTextHintProvider(KTextEditor::TextHintProvider * provider)3944 void KateViewInternal::unregisterTextHintProvider(KTextEditor::TextHintProvider *provider)
3945 {
3946     const auto it = std::find(m_textHintProviders.cbegin(), m_textHintProviders.cend(), provider);
3947     if (it != m_textHintProviders.cend()) {
3948         m_textHintProviders.erase(it);
3949     }
3950 
3951     if (m_textHintProviders.empty()) {
3952         m_textHintTimer.stop();
3953     }
3954 }
3955 
setTextHintDelay(int delay)3956 void KateViewInternal::setTextHintDelay(int delay)
3957 {
3958     if (delay <= 0) {
3959         m_textHintDelay = 200; // ms
3960     } else {
3961         m_textHintDelay = delay; // ms
3962     }
3963 }
3964 
textHintDelay() const3965 int KateViewInternal::textHintDelay() const
3966 {
3967     return m_textHintDelay;
3968 }
3969 
textHintsEnabled()3970 bool KateViewInternal::textHintsEnabled()
3971 {
3972     return !m_textHintProviders.empty();
3973 }
3974 
3975 // BEGIN EDIT STUFF
editStart()3976 void KateViewInternal::editStart()
3977 {
3978     editSessionNumber++;
3979 
3980     if (editSessionNumber > 1) {
3981         return;
3982     }
3983 
3984     editIsRunning = true;
3985     editOldCursor = m_cursor;
3986     editOldSelection = view()->selectionRange();
3987 }
3988 
editEnd(int editTagLineStart,int editTagLineEnd,bool tagFrom)3989 void KateViewInternal::editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom)
3990 {
3991     if (editSessionNumber == 0) {
3992         return;
3993     }
3994 
3995     editSessionNumber--;
3996 
3997     if (editSessionNumber > 0) {
3998         return;
3999     }
4000 
4001     // fix start position, might have moved from column 0
4002     // try to clever calculate the right start column for the tricky dyn word wrap case
4003     int col = 0;
4004     if (view()->dynWordWrap()) {
4005         if (KateLineLayoutPtr layout = cache()->line(startLine())) {
4006             int index = layout->viewLineForColumn(startPos().column());
4007             if (index >= 0 && index < layout->viewLineCount()) {
4008                 col = layout->viewLine(index).startCol();
4009             }
4010         }
4011     }
4012     m_startPos.setPosition(startLine(), col);
4013 
4014     if (tagFrom && (editTagLineStart <= int(view()->textFolding().visibleLineToLine(startLine())))) {
4015         tagAll();
4016     } else {
4017         tagLines(editTagLineStart, tagFrom ? qMax(doc()->lastLine() + 1, editTagLineEnd) : editTagLineEnd, true);
4018     }
4019 
4020     if (editOldCursor == m_cursor.toCursor()) {
4021         updateBracketMarks();
4022     }
4023 
4024     updateView(true);
4025 
4026     if (editOldCursor != m_cursor.toCursor() || m_view == doc()->activeView()) {
4027         // Only scroll the view to the cursor if the insertion happens at the cursor.
4028         // This might not be the case for e.g. collaborative editing, when a remote user
4029         // inserts text at a position not at the caret.
4030         if (m_cursor.line() >= editTagLineStart && m_cursor.line() <= editTagLineEnd) {
4031             m_madeVisible = false;
4032             updateCursor(m_cursor, true);
4033         }
4034     }
4035 
4036     // selection changed?
4037     // fixes bug 316226
4038     if (editOldSelection != view()->selectionRange()
4039         || (editOldSelection.isValid() && !editOldSelection.isEmpty()
4040             && !(editTagLineStart > editOldSelection.end().line() && editTagLineEnd < editOldSelection.start().line()))) {
4041         Q_EMIT view()->selectionChanged(m_view);
4042     }
4043 
4044     editIsRunning = false;
4045 }
4046 
editSetCursor(const KTextEditor::Cursor _cursor)4047 void KateViewInternal::editSetCursor(const KTextEditor::Cursor _cursor)
4048 {
4049     if (m_cursor.toCursor() != _cursor) {
4050         m_cursor.setPosition(_cursor);
4051     }
4052 }
4053 // END
4054 
viewSelectionChanged()4055 void KateViewInternal::viewSelectionChanged()
4056 {
4057     if (!view()->selection()) {
4058         m_selectAnchor = KTextEditor::Cursor::invalid();
4059     } else {
4060         m_selectAnchor = view()->selectionRange().start();
4061     }
4062     // Do NOT nuke the entire range! The reason is that a shift+DC selection
4063     // might (correctly) set the range to be empty (i.e. start() == end()), and
4064     // subsequent dragging might shrink the selection into non-existence. When
4065     // this happens, we use the cached end to restore the cached start so that
4066     // updateSelection is not confused. See also comments in updateSelection.
4067     m_selectionCached.setStart(KTextEditor::Cursor::invalid());
4068 }
4069 
isUserSelecting() const4070 bool KateViewInternal::isUserSelecting() const
4071 {
4072     return m_selChangedByUser;
4073 }
4074 
cache() const4075 KateLayoutCache *KateViewInternal::cache() const
4076 {
4077     return m_layoutCache;
4078 }
4079 
toRealCursor(const KTextEditor::Cursor virtualCursor) const4080 KTextEditor::Cursor KateViewInternal::toRealCursor(const KTextEditor::Cursor virtualCursor) const
4081 {
4082     return KTextEditor::Cursor(view()->textFolding().visibleLineToLine(virtualCursor.line()), virtualCursor.column());
4083 }
4084 
toVirtualCursor(const KTextEditor::Cursor realCursor) const4085 KTextEditor::Cursor KateViewInternal::toVirtualCursor(const KTextEditor::Cursor realCursor) const
4086 {
4087     // only convert valid lines, folding doesn't like invalid input!
4088     // don't validate whole cursor, column might be -1
4089     if (realCursor.line() < 0) {
4090         return KTextEditor::Cursor::invalid();
4091     }
4092 
4093     return KTextEditor::Cursor(view()->textFolding().lineToVisibleLine(realCursor.line()), realCursor.column());
4094 }
4095 
renderer() const4096 KateRenderer *KateViewInternal::renderer() const
4097 {
4098     return view()->renderer();
4099 }
4100 
mouseMoved()4101 void KateViewInternal::mouseMoved()
4102 {
4103     view()->notifyMousePositionChanged(m_mouse);
4104     view()->updateRangesIn(KTextEditor::Attribute::ActivateMouseIn);
4105 }
4106 
cursorMoved()4107 void KateViewInternal::cursorMoved()
4108 {
4109     view()->updateRangesIn(KTextEditor::Attribute::ActivateCaretIn);
4110 
4111 #ifndef QT_NO_ACCESSIBILITY
4112     if (QAccessible::isActive()) {
4113         QAccessibleTextCursorEvent ev(this, static_cast<KateViewAccessible *>(QAccessible::queryAccessibleInterface(this))->positionFromCursor(this, m_cursor));
4114         QAccessible::updateAccessibility(&ev);
4115     }
4116 #endif
4117 }
4118 
doc()4119 KTextEditor::DocumentPrivate *KateViewInternal::doc()
4120 {
4121     return m_view->doc();
4122 }
4123 
doc() const4124 KTextEditor::DocumentPrivate *KateViewInternal::doc() const
4125 {
4126     return m_view->doc();
4127 }
4128 
rangeAffectsView(const KTextEditor::Range & range,bool realCursors) const4129 bool KateViewInternal::rangeAffectsView(const KTextEditor::Range &range, bool realCursors) const
4130 {
4131     int startLine = KateViewInternal::startLine();
4132     int endLine = startLine + (int)m_visibleLineCount;
4133 
4134     if (realCursors) {
4135         startLine = (int)view()->textFolding().visibleLineToLine(startLine);
4136         endLine = (int)view()->textFolding().visibleLineToLine(endLine);
4137     }
4138 
4139     return (range.end().line() >= startLine) || (range.start().line() <= endLine);
4140 }
4141 
4142 // BEGIN IM INPUT STUFF
inputMethodQuery(Qt::InputMethodQuery query) const4143 QVariant KateViewInternal::inputMethodQuery(Qt::InputMethodQuery query) const
4144 {
4145     switch (query) {
4146     case Qt::ImCursorRectangle: {
4147         // Cursor placement code is changed for Asian input method that
4148         // shows candidate window. This behavior is same as Qt/E 2.3.7
4149         // which supports Asian input methods. Asian input methods need
4150         // start point of IM selection text to place candidate window as
4151         // adjacent to the selection text.
4152         //
4153         // in Qt5, cursor rectangle is used as QRectF internally, and it
4154         // will be checked by QRectF::isValid(), which will mark rectangle
4155         // with width == 0 or height == 0 as invalid.
4156         auto lineHeight = renderer()->lineHeight();
4157         return QRect(cursorToCoordinate(m_cursor, true, false), QSize(1, lineHeight ? lineHeight : 1));
4158     }
4159 
4160     case Qt::ImFont:
4161         return renderer()->currentFont();
4162 
4163     case Qt::ImCursorPosition:
4164         return m_cursor.column();
4165 
4166     case Qt::ImAnchorPosition:
4167         // If selectAnchor is at the same line, return the real anchor position
4168         // Otherwise return the same position of cursor
4169         if (view()->selection() && m_selectAnchor.line() == m_cursor.line()) {
4170             return m_selectAnchor.column();
4171         } else {
4172             return m_cursor.column();
4173         }
4174 
4175     case Qt::ImSurroundingText:
4176         if (Kate::TextLine l = doc()->kateTextLine(m_cursor.line())) {
4177             return l->string();
4178         } else {
4179             return QString();
4180         }
4181 
4182     case Qt::ImCurrentSelection:
4183         if (view()->selection()) {
4184             return view()->selectionText();
4185         } else {
4186             return QString();
4187         }
4188     default:
4189         /* values: ImMaximumTextLength */
4190         break;
4191     }
4192 
4193     return QWidget::inputMethodQuery(query);
4194 }
4195 
inputMethodEvent(QInputMethodEvent * e)4196 void KateViewInternal::inputMethodEvent(QInputMethodEvent *e)
4197 {
4198     if (doc()->readOnly()) {
4199         e->ignore();
4200         return;
4201     }
4202 
4203     // qCDebug(LOG_KTE) << "Event: cursor" << m_cursor << "commit" << e->commitString() << "preedit" << e->preeditString() << "replacement start" <<
4204     // e->replacementStart() << "length" << e->replacementLength();
4205 
4206     if (!m_imPreeditRange) {
4207         m_imPreeditRange.reset(
4208             doc()->newMovingRange(KTextEditor::Range(m_cursor, m_cursor), KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight));
4209     }
4210 
4211     if (!m_imPreeditRange->toRange().isEmpty()) {
4212         doc()->inputMethodStart();
4213         doc()->removeText(*m_imPreeditRange);
4214         doc()->inputMethodEnd();
4215     }
4216 
4217     if (!e->commitString().isEmpty() || e->replacementLength() || !e->preeditString().isEmpty()) {
4218         view()->removeSelectedText();
4219     }
4220 
4221     if (!e->commitString().isEmpty() || e->replacementLength()) {
4222         KTextEditor::Range preeditRange = *m_imPreeditRange;
4223 
4224         KTextEditor::Cursor start(m_imPreeditRange->start().line(), m_imPreeditRange->start().column() + e->replacementStart());
4225         KTextEditor::Cursor removeEnd = start + KTextEditor::Cursor(0, e->replacementLength());
4226 
4227         doc()->editStart();
4228         if (start != removeEnd) {
4229             doc()->removeText(KTextEditor::Range(start, removeEnd));
4230         }
4231 
4232         // if the input method event is text that should be inserted, call KTextEditor::DocumentPrivate::typeChars()
4233         // with the text. that method will handle the input and take care of overwrite mode, etc.
4234         doc()->typeChars(m_view, e->commitString());
4235 
4236         doc()->editEnd();
4237 
4238         // Revert to the same range as above
4239         m_imPreeditRange->setRange(preeditRange);
4240     }
4241 
4242     if (!e->preeditString().isEmpty()) {
4243         doc()->inputMethodStart();
4244         doc()->insertText(m_imPreeditRange->start(), e->preeditString());
4245         doc()->inputMethodEnd();
4246         // The preedit range gets automatically repositioned
4247     }
4248 
4249     // Finished this input method context?
4250     if (m_imPreeditRange && e->preeditString().isEmpty()) {
4251         // delete the range and reset the pointer
4252         m_imPreeditRange.reset();
4253         m_imPreeditRangeChildren.clear();
4254 
4255         if (QApplication::cursorFlashTime() > 0) {
4256             renderer()->setDrawCaret(false);
4257         }
4258         renderer()->setCaretOverrideColor(QColor());
4259 
4260         e->accept();
4261         return;
4262     }
4263 
4264     KTextEditor::Cursor newCursor = m_cursor;
4265     bool hideCursor = false;
4266     QColor caretColor;
4267 
4268     if (m_imPreeditRange) {
4269         m_imPreeditRangeChildren.clear();
4270 
4271         int decorationColumn = 0;
4272         const auto &attributes = e->attributes();
4273         for (auto &a : attributes) {
4274             if (a.type == QInputMethodEvent::Cursor) {
4275                 newCursor = m_imPreeditRange->start() + KTextEditor::Cursor(0, a.start);
4276                 hideCursor = !a.length;
4277                 QColor c = qvariant_cast<QColor>(a.value);
4278                 if (c.isValid()) {
4279                     caretColor = c;
4280                 }
4281 
4282             } else if (a.type == QInputMethodEvent::TextFormat) {
4283                 QTextCharFormat f = qvariant_cast<QTextFormat>(a.value).toCharFormat();
4284 
4285                 if (f.isValid() && decorationColumn <= a.start) {
4286                     const KTextEditor::MovingCursor &preEditRangeStart = m_imPreeditRange->start();
4287                     const int startLine = preEditRangeStart.line();
4288                     const int startCol = preEditRangeStart.column();
4289                     KTextEditor::Range fr(startLine, startCol + a.start, startLine, startCol + a.start + a.length);
4290                     std::unique_ptr<KTextEditor::MovingRange> formatRange(doc()->newMovingRange(fr));
4291                     KTextEditor::Attribute::Ptr attribute(new KTextEditor::Attribute());
4292                     attribute->merge(f);
4293                     formatRange->setAttribute(attribute);
4294                     decorationColumn = a.start + a.length;
4295                     m_imPreeditRangeChildren.push_back(std::move(formatRange));
4296                 }
4297             }
4298         }
4299     }
4300 
4301     renderer()->setDrawCaret(hideCursor);
4302     renderer()->setCaretOverrideColor(caretColor);
4303 
4304     if (newCursor != m_cursor.toCursor()) {
4305         updateCursor(newCursor);
4306     }
4307 
4308     e->accept();
4309 }
4310 
4311 // END IM INPUT STUFF
4312 
flashChar(const KTextEditor::Cursor pos,KTextEditor::Attribute::Ptr attribute)4313 void KateViewInternal::flashChar(const KTextEditor::Cursor pos, KTextEditor::Attribute::Ptr attribute)
4314 {
4315     Q_ASSERT(pos.isValid());
4316     Q_ASSERT(attribute.constData());
4317 
4318     // if line is folded away, do nothing
4319     if (!view()->textFolding().isLineVisible(pos.line())) {
4320         return;
4321     }
4322 
4323     KTextEditor::Range range(pos, KTextEditor::Cursor(pos.line(), pos.column() + 1));
4324     if (m_textAnimation) {
4325         m_textAnimation->deleteLater();
4326     }
4327     m_textAnimation = new KateTextAnimation(range, std::move(attribute), this);
4328 }
4329 
showBracketMatchPreview()4330 void KateViewInternal::showBracketMatchPreview()
4331 {
4332     // only show when main window is active
4333     if (window() && !window()->isActiveWindow()) {
4334         return;
4335     }
4336 
4337     const KTextEditor::Cursor openBracketCursor = m_bmStart->start();
4338     // 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
4339     if (m_cursor == openBracketCursor || toVirtualCursor(openBracketCursor).line() >= startLine() || m_cursor.line() - startLine() < 2) {
4340         hideBracketMatchPreview();
4341         return;
4342     }
4343 
4344     if (!m_bmPreview) {
4345         m_bmPreview.reset(new KateTextPreview(m_view, this));
4346         m_bmPreview->setAttribute(Qt::WA_ShowWithoutActivating);
4347         m_bmPreview->setFrameStyle(QFrame::Box);
4348     }
4349 
4350     const int previewLine = openBracketCursor.line();
4351     KateRenderer *const renderer_ = renderer();
4352     KateLineLayoutPtr lineLayout(new KateLineLayout(*renderer_));
4353     lineLayout->setLine(previewLine, -1);
4354 
4355     // If the opening bracket is on its own line, start preview at the line above it instead (where the context is likely to be)
4356     const int col = lineLayout->textLine()->firstChar();
4357     if (previewLine > 0 && (col == -1 || col == openBracketCursor.column())) {
4358         lineLayout->setLine(previewLine - 1, lineLayout->virtualLine() - 1);
4359     }
4360 
4361     renderer_->layoutLine(lineLayout, -1 /* no wrap */, false /* no layout cache */);
4362     const int lineWidth =
4363         qBound(m_view->width() / 5, int(lineLayout->width() + renderer_->spaceWidth() * 2), m_view->width() - m_leftBorder->width() - m_lineScroll->width());
4364     m_bmPreview->resize(lineWidth, renderer_->lineHeight() * 2);
4365     const QPoint topLeft = mapToGlobal(QPoint(0, 0));
4366     m_bmPreview->move(topLeft.x(), topLeft.y());
4367     m_bmPreview->setLine(lineLayout->virtualLine());
4368     m_bmPreview->setCenterView(false);
4369     m_bmPreview->raise();
4370     m_bmPreview->show();
4371 }
4372 
hideBracketMatchPreview()4373 void KateViewInternal::hideBracketMatchPreview()
4374 {
4375     m_bmPreview.reset();
4376 }
4377 
documentTextInserted(KTextEditor::Document * document,const KTextEditor::Range & range)4378 void KateViewInternal::documentTextInserted(KTextEditor::Document *document, const KTextEditor::Range &range)
4379 {
4380 #ifndef QT_NO_ACCESSIBILITY
4381     if (QAccessible::isActive()) {
4382         QAccessibleTextInsertEvent ev(this,
4383                                       static_cast<KateViewAccessible *>(QAccessible::queryAccessibleInterface(this))->positionFromCursor(this, range.start()),
4384                                       document->text(range));
4385         QAccessible::updateAccessibility(&ev);
4386     }
4387 #endif
4388 }
4389 
documentTextRemoved(KTextEditor::Document *,const KTextEditor::Range & range,const QString & oldText)4390 void KateViewInternal::documentTextRemoved(KTextEditor::Document * /*document*/, const KTextEditor::Range &range, const QString &oldText)
4391 {
4392 #ifndef QT_NO_ACCESSIBILITY
4393     if (QAccessible::isActive()) {
4394         QAccessibleTextRemoveEvent ev(this,
4395                                       static_cast<KateViewAccessible *>(QAccessible::queryAccessibleInterface(this))->positionFromCursor(this, range.start()),
4396                                       oldText);
4397         QAccessible::updateAccessibility(&ev);
4398     }
4399 #endif
4400 }
4401 
inlineNoteRect(const KateInlineNoteData & noteData) const4402 QRect KateViewInternal::inlineNoteRect(const KateInlineNoteData &noteData) const
4403 {
4404     KTextEditor::InlineNote note(noteData);
4405     // compute note width and position
4406     const auto noteWidth = note.width();
4407     auto noteCursor = note.position();
4408 
4409     // The cursor might be outside of the text. In that case, clamp it to the text and
4410     // later on add the missing x offset.
4411     const auto lineLength = view()->document()->lineLength(noteCursor.line());
4412     int extraOffset = -noteWidth;
4413     if (noteCursor.column() == lineLength) {
4414         extraOffset = 0;
4415     } else if (noteCursor.column() > lineLength) {
4416         extraOffset = (noteCursor.column() - lineLength) * renderer()->spaceWidth();
4417         noteCursor.setColumn(lineLength);
4418     }
4419     auto noteStartPos = mapToGlobal(cursorToCoordinate(noteCursor, true, false));
4420 
4421     // compute the note's rect
4422     auto globalNoteRect = QRect(noteStartPos + QPoint{extraOffset, 0}, QSize(noteWidth, renderer()->lineHeight()));
4423 
4424     return globalNoteRect;
4425 }
4426 
inlineNoteAt(const QPoint & globalPos) const4427 KateInlineNoteData KateViewInternal::inlineNoteAt(const QPoint &globalPos) const
4428 {
4429     // compute the associated cursor to get the right line
4430     const int line = coordinatesToCursor(mapFromGlobal(globalPos)).line();
4431     const auto inlineNotes = view()->inlineNotes(line);
4432     // loop over all notes and check if the point is inside it
4433     for (const auto &note : inlineNotes) {
4434         auto globalNoteRect = inlineNoteRect(note);
4435         if (globalNoteRect.contains(globalPos)) {
4436             return note;
4437         }
4438     }
4439     // none found -- return an invalid note
4440     return {};
4441 }
4442