KTextEditor

katecompletionwidget.cpp
1/*
2 SPDX-FileCopyrightText: 2005-2006 Hamish Rodda <rodda@kde.org>
3 SPDX-FileCopyrightText: 2007-2008 David Nolden <david.nolden.kdevelop@art-master.de>
4 SPDX-FileCopyrightText: 2022-2024 Waqar Ahmed <waqar.17a@gmail.com>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "katecompletionwidget.h"
10
11#include <ktexteditor/codecompletionmodelcontrollerinterface.h>
12
13#include "kateconfig.h"
14#include "katedocument.h"
15#include "kateglobal.h"
16#include "katerenderer.h"
17#include "kateview.h"
18
19#include "documentation_tip.h"
20#include "kateargumenthintmodel.h"
21#include "kateargumenthinttree.h"
22#include "katecompletionmodel.h"
23#include "katecompletiontree.h"
24#include "katepartdebug.h"
25
26#include <QAbstractScrollArea>
27#include <QApplication>
28#include <QBoxLayout>
29#include <QHeaderView>
30#include <QLabel>
31#include <QPushButton>
32#include <QScreen>
33#include <QScrollBar>
34#include <QSizeGrip>
35#include <QTimer>
36#include <QToolButton>
37
38const bool hideAutomaticCompletionOnExactMatch = true;
39
40#define CALLCI(WHAT, WHATELSE, WHAT2, model, FUNC) \
41 { \
42 static KTextEditor::CodeCompletionModelControllerInterface defaultIf; \
43 KTextEditor::CodeCompletionModelControllerInterface *ret = qobject_cast<KTextEditor::CodeCompletionModelControllerInterface *>(model); \
44 if (!ret) { \
45 WHAT2 defaultIf.FUNC; \
46 } else \
47 WHAT2 ret->FUNC; \
48 }
49
51{
52 CALLCI(return, , return, model, completionRange(view, cursor));
53}
54
56{
57 CALLCI(, return range, return, model, updateCompletionRange(view, range));
58}
59
60static QString _filterString(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view, const KTextEditor::Range &range, KTextEditor::Cursor cursor)
61{
62 CALLCI(return, , return, model, filterString(view, range, cursor));
63}
64
65static bool
66_shouldAbortCompletion(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view, const KTextEditor::Range &range, const QString &currentCompletion)
67{
68 CALLCI(return, , return, model, shouldAbortCompletion(view, range, currentCompletion));
69}
70
71static void _aborted(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view)
72{
73 CALLCI(return, , return, model, aborted(view));
74}
75
76static bool _shouldStartCompletion(KTextEditor::CodeCompletionModel *model,
78 const QString &automaticInvocationLine,
79 bool m_lastInsertionByUser,
81{
82 CALLCI(return, , return, model, shouldStartCompletion(view, automaticInvocationLine, m_lastInsertionByUser, cursor));
83}
84
85KateCompletionWidget::KateCompletionWidget(KTextEditor::ViewPrivate *parent)
86 : QFrame(parent)
87 , m_presentationModel(new KateCompletionModel(this))
88 , m_view(parent)
89 , m_entryList(new KateCompletionTree(this))
90 , m_argumentHintModel(new KateArgumentHintModel(m_presentationModel))
91 , m_argumentHintWidget(new ArgumentHintWidget(m_argumentHintModel, parent->renderer()->currentFont(), this, this))
92 , m_docTip(new DocTip(this))
93 , m_automaticInvocationDelay(100)
94 , m_lastInsertionByUser(false)
95 , m_isSuspended(false)
96 , m_dontShowArgumentHints(false)
97 , m_needShow(false)
98 , m_hadCompletionNavigation(false)
99 , m_noAutoHide(false)
100 , m_completionEditRunning(false)
101 , m_expandedAddedHeightBase(0)
102 , m_lastInvocationType(KTextEditor::CodeCompletionModel::AutomaticInvocation)
103{
104 if (parent->mainWindow() != KTextEditor::EditorPrivate::self()->dummyMainWindow() && parent->mainWindow()->window()) {
105 setParent(parent->mainWindow()->window());
106 } else if (auto w = m_view->window()) {
107 setParent(w);
108 } else if (auto w = QApplication::activeWindow()) {
109 setParent(w);
110 } else {
111 setParent(parent);
112 }
113 m_docTip->setParent(this->parentWidget());
114 parentWidget()->installEventFilter(this);
115
116 setFrameStyle(QFrame::Box | QFrame::Raised);
117 setLineWidth(1);
118
119 m_entryList->setModel(m_presentationModel);
120 m_entryList->setColumnWidth(0, 0); // These will be determined automatically in KateCompletionTree::resizeColumns
121 m_entryList->setColumnWidth(1, 0);
122 m_entryList->setColumnWidth(2, 0);
123
124 m_argumentHintWidget->setParent(this->parentWidget());
125
126 // trigger completion on double click on completion list
127 connect(m_entryList, &KateCompletionTree::doubleClicked, this, &KateCompletionWidget::execute);
128
129 connect(view(), &KTextEditor::ViewPrivate::focusOut, this, &KateCompletionWidget::viewFocusOut);
130
131 m_automaticInvocationTimer = new QTimer(this);
132 m_automaticInvocationTimer->setSingleShot(true);
133 connect(m_automaticInvocationTimer, &QTimer::timeout, this, &KateCompletionWidget::automaticInvocation);
134
135 // Keep branches expanded
136 connect(m_presentationModel, &KateCompletionModel::modelReset, this, &KateCompletionWidget::modelReset);
137 connect(m_presentationModel, &KateCompletionModel::rowsInserted, this, &KateCompletionWidget::rowsInserted);
138 connect(m_argumentHintModel, &KateArgumentHintModel::contentStateChanged, this, &KateCompletionWidget::argumentHintsChanged);
139
140 // No smart lock, no queued connects
141 connect(view(), &KTextEditor::ViewPrivate::cursorPositionChanged, this, &KateCompletionWidget::cursorPositionChanged);
142 connect(view(), &KTextEditor::ViewPrivate::verticalScrollPositionChanged, this, [this] {
143 abortCompletion();
144 });
145
146 // connect to all possible editing primitives
147 connect(view()->doc(), &KTextEditor::Document::lineWrapped, this, &KateCompletionWidget::wrapLine);
148 connect(view()->doc(), &KTextEditor::Document::lineUnwrapped, this, &KateCompletionWidget::unwrapLine);
149 connect(view()->doc(), &KTextEditor::Document::textInserted, this, &KateCompletionWidget::insertText);
150 connect(view()->doc(), &KTextEditor::Document::textRemoved, this, &KateCompletionWidget::removeText);
151
152 // This is a non-focus widget, it is passed keyboard input from the view
153
154 // We need to do this, because else the focus goes to nirvana without any control when the completion-widget is clicked.
155 setFocusPolicy(Qt::ClickFocus);
156
157 const auto children = findChildren<QWidget *>();
158 for (QWidget *childWidget : children) {
159 childWidget->setFocusPolicy(Qt::NoFocus);
160 }
161
162 // Position the entry-list so a frame can be drawn around it
163 m_entryList->move(frameWidth(), frameWidth());
164
165 hide();
166 m_docTip->setVisible(false);
167}
168
169KateCompletionWidget::~KateCompletionWidget()
170{
171 // ensure no slot triggered during destruction => else we access already invalidated stuff
172 m_presentationModel->disconnect(this);
173 m_argumentHintModel->disconnect(this);
174
175 delete m_docTip;
176}
177
178void KateCompletionWidget::viewFocusOut()
179{
180 QWidget *toplevels[3] = {m_entryList, m_docTip, m_argumentHintWidget};
181 if (!std::any_of(std::begin(toplevels), std::end(toplevels), [](QWidget *w) {
182 auto fw = QApplication::focusWidget();
183 return fw == w || w->isAncestorOf(fw);
184 })) {
185 abortCompletion();
186 }
187}
188
189void KateCompletionWidget::focusOutEvent(QFocusEvent *)
190{
191 abortCompletion();
192}
193
194void KateCompletionWidget::modelContentChanged()
195{
196 ////qCDebug(LOG_KTE)<<">>>>>>>>>>>>>>>>";
197 if (m_completionRanges.isEmpty()) {
198 // qCDebug(LOG_KTE) << "content changed, but no completion active";
199 abortCompletion();
200 return;
201 }
202
203 if (!view()->hasFocus()) {
204 // qCDebug(LOG_KTE) << "view does not have focus";
205 return;
206 }
207
208 if (!m_waitingForReset.isEmpty()) {
209 // qCDebug(LOG_KTE) << "waiting for" << m_waitingForReset.size() << "completion-models to reset";
210 return;
211 }
212
213 int realItemCount = 0;
214 const auto completionModels = m_presentationModel->completionModels();
215 for (KTextEditor::CodeCompletionModel *model : completionModels) {
216 realItemCount += model->rowCount();
217 }
218 if (!m_isSuspended && ((isHidden() && m_argumentHintWidget->isHidden()) || m_needShow) && realItemCount != 0) {
219 m_needShow = false;
220 updateAndShow();
221 }
222
223 if (m_argumentHintModel->rowCount(QModelIndex()) == 0) {
224 m_argumentHintWidget->hide();
225 }
226
227 if (m_presentationModel->rowCount(QModelIndex()) == 0) {
228 hide();
229 }
230
231 // For automatic invocations, only autoselect first completion entry when enabled in the config
232 if (m_lastInvocationType != KTextEditor::CodeCompletionModel::AutomaticInvocation || view()->config()->automaticCompletionPreselectFirst()) {
233 m_entryList->setCurrentIndex(model()->index(0, 0));
234 }
235 // With each filtering items can be added or removed, so we have to reset the current index here so we always have a selected item
236 if (!model()->indexIsItem(m_entryList->currentIndex())) {
237 QModelIndex firstIndex = model()->index(0, 0, m_entryList->currentIndex());
238 m_entryList->setCurrentIndex(firstIndex);
239 // m_entryList->scrollTo(firstIndex, QAbstractItemView::PositionAtTop);
240 }
241
242 updateHeight();
243
244 // New items for the argument-hint tree may have arrived, so check whether it needs to be shown
245 if (m_argumentHintWidget->isHidden() && !m_dontShowArgumentHints && m_argumentHintModel->rowCount(QModelIndex()) != 0) {
246 m_argumentHintWidget->positionAndShow();
247 }
248
249 if (!m_noAutoHide && hideAutomaticCompletionOnExactMatch && !isHidden() && m_lastInvocationType == KTextEditor::CodeCompletionModel::AutomaticInvocation
250 && m_presentationModel->shouldMatchHideCompletionList()) {
251 hide();
252 } else if (isHidden() && !m_presentationModel->shouldMatchHideCompletionList() && m_presentationModel->rowCount(QModelIndex())) {
253 show();
254 }
255}
256
257KateArgumentHintModel *KateCompletionWidget::argumentHintModel() const
258{
259 return m_argumentHintModel;
260}
261
262const KateCompletionModel *KateCompletionWidget::model() const
263{
264 return m_presentationModel;
265}
266
267KateCompletionModel *KateCompletionWidget::model()
268{
269 return m_presentationModel;
270}
271
272void KateCompletionWidget::rowsInserted(const QModelIndex &parent, int rowFrom, int rowEnd)
273{
274 m_entryList->setAnimated(false);
275
276 if (!parent.isValid()) {
277 for (int i = rowFrom; i <= rowEnd; ++i) {
278 m_entryList->expand(m_presentationModel->index(i, 0, parent));
279 }
280 }
281}
282
283KTextEditor::ViewPrivate *KateCompletionWidget::view() const
284{
285 return m_view;
286}
287
288void KateCompletionWidget::argumentHintsChanged(bool hasContent)
289{
290 m_dontShowArgumentHints = !hasContent;
291
292 if (m_dontShowArgumentHints) {
293 m_argumentHintWidget->hide();
294 } else {
295 updateArgumentHintGeometry();
296 }
297}
298
299void KateCompletionWidget::startCompletion(KTextEditor::CodeCompletionModel::InvocationType invocationType,
301{
302 if (invocationType == KTextEditor::CodeCompletionModel::UserInvocation) {
303 abortCompletion();
304 }
305 startCompletion(KTextEditor::Range(KTextEditor::Cursor(-1, -1), KTextEditor::Cursor(-1, -1)), models, invocationType);
306}
307
308void KateCompletionWidget::deleteCompletionRanges()
309{
310 for (const CompletionRange &r : std::as_const(m_completionRanges)) {
311 delete r.range;
312 }
313 m_completionRanges.clear();
314}
315
316void KateCompletionWidget::startCompletion(KTextEditor::Range word,
318 KTextEditor::CodeCompletionModel::InvocationType invocationType)
319{
321 if (model) {
322 models << model;
323 } else {
324 models = m_sourceModels;
325 }
326 startCompletion(word, models, invocationType);
327}
328
329void KateCompletionWidget::startCompletion(KTextEditor::Range word,
331 KTextEditor::CodeCompletionModel::InvocationType invocationType)
332{
333 ////qCDebug(LOG_KTE)<<"============";
334
335 m_isSuspended = false;
336 m_needShow = true;
337
338 if (m_completionRanges.isEmpty()) {
339 m_noAutoHide = false; // Re-enable auto-hide on every clean restart of the completion
340 }
341
342 m_lastInvocationType = invocationType;
343
344 disconnect(this->model(), &KateCompletionModel::layoutChanged, this, &KateCompletionWidget::modelContentChanged);
345 disconnect(this->model(), &KateCompletionModel::modelReset, this, &KateCompletionWidget::modelContentChanged);
346
347 m_dontShowArgumentHints = true;
348
349 QList<KTextEditor::CodeCompletionModel *> models = (modelsToStart.isEmpty() ? m_sourceModels : modelsToStart);
350
351 for (auto it = m_completionRanges.keyBegin(), end = m_completionRanges.keyEnd(); it != end; ++it) {
353 if (!models.contains(model)) {
354 models << model;
355 }
356 }
357
358 m_presentationModel->clearCompletionModels();
359
360 if (invocationType == KTextEditor::CodeCompletionModel::UserInvocation) {
361 deleteCompletionRanges();
362 }
363
364 for (KTextEditor::CodeCompletionModel *model : std::as_const(models)) {
365 KTextEditor::Range range;
366 if (word.isValid()) {
367 range = word;
368 // qCDebug(LOG_KTE)<<"word is used";
369 } else {
370 range = _completionRange(model, view(), view()->cursorPosition());
371 // qCDebug(LOG_KTE)<<"completionRange has been called, cursor pos is"<<view()->cursorPosition();
372 }
373 // qCDebug(LOG_KTE)<<"range is"<<range;
374 if (!range.isValid()) {
375 if (m_completionRanges.contains(model)) {
376 KTextEditor::MovingRange *oldRange = m_completionRanges[model].range;
377 // qCDebug(LOG_KTE)<<"removing completion range 1";
378 m_completionRanges.remove(model);
379 delete oldRange;
380 }
381 models.removeAll(model);
382 continue;
383 }
384 if (m_completionRanges.contains(model)) {
385 if (*m_completionRanges[model].range == range) {
386 continue; // Leave it running as it is
387 } else { // delete the range that was used previously
388 KTextEditor::MovingRange *oldRange = m_completionRanges[model].range;
389 // qCDebug(LOG_KTE)<<"removing completion range 2";
390 m_completionRanges.remove(model);
391 delete oldRange;
392 }
393 }
394
395 connect(model, &KTextEditor::CodeCompletionModel::waitForReset, this, &KateCompletionWidget::waitForModelReset);
396
397 // qCDebug(LOG_KTE)<<"Before completion invoke: range:"<<range;
398 model->completionInvoked(view(), range, invocationType);
399
400 disconnect(model, &KTextEditor::CodeCompletionModel::waitForReset, this, &KateCompletionWidget::waitForModelReset);
401
402 m_completionRanges[model] =
403 CompletionRange(view()->doc()->newMovingRange(range, KTextEditor::MovingRange::ExpandRight | KTextEditor::MovingRange::ExpandLeft));
404
405 // In automatic invocation mode, hide the completion widget as soon as the position where the completion was started is passed to the left
406 m_completionRanges[model].leftBoundary = view()->cursorPosition();
407
408 // In manual invocation mode, bound the activity either the point from where completion was invoked, or to the start of the range
409 if (invocationType != KTextEditor::CodeCompletionModel::AutomaticInvocation) {
410 if (range.start() < m_completionRanges[model].leftBoundary) {
411 m_completionRanges[model].leftBoundary = range.start();
412 }
413 }
414
415 if (!m_completionRanges[model].range->toRange().isValid()) {
416 qCWarning(LOG_KTE) << "Could not construct valid smart-range from" << range << "instead got" << *m_completionRanges[model].range;
417 abortCompletion();
418 return;
419 }
420 }
421
422 m_presentationModel->setCompletionModels(models);
423
424 cursorPositionChanged();
425
426 if (!m_completionRanges.isEmpty()) {
427 connect(this->model(), &KateCompletionModel::layoutChanged, this, &KateCompletionWidget::modelContentChanged);
428 connect(this->model(), &KateCompletionModel::modelReset, this, &KateCompletionWidget::modelContentChanged);
429 // Now that all models have been notified, check whether the widget should be displayed instantly
430 modelContentChanged();
431 } else {
432 abortCompletion();
433 }
434}
435
436QString KateCompletionWidget::tailString() const
437{
438 if (!KateViewConfig::global()->wordCompletionRemoveTail()) {
439 return QString();
440 }
441
442 const int line = view()->cursorPosition().line();
443 const int column = view()->cursorPosition().column();
444
445 const QString text = view()->document()->line(line);
446
448 static const QRegularExpression findWordEnd(QStringLiteral("^[_\\w]*\\b"), options);
449
450 QRegularExpressionMatch match = findWordEnd.match(text.mid(column));
451 if (match.hasMatch()) {
452 return match.captured(0);
453 }
454 return QString();
455}
456
457void KateCompletionWidget::waitForModelReset()
458{
459 KTextEditor::CodeCompletionModel *senderModel = qobject_cast<KTextEditor::CodeCompletionModel *>(sender());
460 if (!senderModel) {
461 qCWarning(LOG_KTE) << "waitForReset signal from bad model";
462 return;
463 }
464 m_waitingForReset.insert(senderModel);
465}
466
467void KateCompletionWidget::updateAndShow()
468{
469 // qCDebug(LOG_KTE)<<"*******************************************";
470 if (!view()->hasFocus()) {
471 qCDebug(LOG_KTE) << "view does not have focus";
472 return;
473 }
474
475 setUpdatesEnabled(false);
476
477 modelReset();
478
479 m_argumentHintModel->buildRows();
480 if (m_argumentHintModel->rowCount(QModelIndex()) != 0) {
481 argumentHintsChanged(true);
482 }
483
484 // update height first
485 updateHeight();
486 // then resize columns afterwards because we need height information
487 m_entryList->resizeColumns(true, true);
488 // lastly update position as now we have height and width
489 updatePosition(true);
490
491 setUpdatesEnabled(true);
492
493 if (m_argumentHintModel->rowCount(QModelIndex())) {
494 updateArgumentHintGeometry();
495 m_argumentHintWidget->positionAndShow();
496 } else {
497 m_argumentHintWidget->hide();
498 }
499
500 if (m_presentationModel->rowCount()
501 && (!m_presentationModel->shouldMatchHideCompletionList() || !hideAutomaticCompletionOnExactMatch
502 || m_lastInvocationType != KTextEditor::CodeCompletionModel::AutomaticInvocation)) {
503 show();
504 } else {
505 hide();
506 }
507}
508
509void KateCompletionWidget::updatePosition(bool force)
510{
511 if (!force && !isCompletionActive()) {
512 return;
513 }
514
515 if (!completionRange()) {
516 return;
517 }
518 const QPoint localCursorCoord = view()->cursorToCoordinate(completionRange()->start());
519 if (localCursorCoord == QPoint(-1, -1)) {
520 // Start of completion range is now off-screen -> abort
521 abortCompletion();
522 return;
523 }
524
525 const QPoint cursorCoordinate = view()->mapToGlobal(localCursorCoord);
526 QPoint p = cursorCoordinate;
527 int x = p.x();
528 int y = p.y();
529
530 y += view()->renderer()->currentFontMetrics().height() + 2;
531
532 const auto windowGeometry = parentWidget()->geometry();
533 if (x + width() > windowGeometry.right()) {
534 // crossing right edge
535 x = windowGeometry.right() - width();
536 }
537 if (x < windowGeometry.left()) {
538 x = windowGeometry.left();
539 }
540
541 if (y + height() > windowGeometry.bottom()) {
542 // move above cursor if we are crossing the bottom
543 y -= height();
544 if (y + height() > cursorCoordinate.y()) {
545 y -= (y + height()) - cursorCoordinate.y();
546 y -= 2;
547 }
548 }
549
551}
552
553void KateCompletionWidget::updateArgumentHintGeometry()
554{
555 if (!m_dontShowArgumentHints) {
556 // Now place the argument-hint widget
557 m_argumentHintWidget->updateGeometry();
558 }
559}
560
561// Checks whether the given model has at least "rows" rows, also searching the second level of the tree.
562bool hasAtLeastNRows(int rows, QAbstractItemModel *model)
563{
564 int count = 0;
565 for (int row = 0; row < model->rowCount(); ++row) {
566 ++count;
567
568 QModelIndex index(model->index(row, 0));
569 if (index.isValid()) {
570 count += model->rowCount(index);
571 }
572
573 if (count > rows) {
574 return true;
575 }
576 }
577 return false;
578}
579
581{
582 QRect geom = geometry();
583
584 constexpr int minBaseHeight = 10;
585 constexpr int maxBaseHeight = 300;
586
587 int baseHeight = 0;
588 int calculatedCustomHeight = 0;
589
590 if (hasAtLeastNRows(15, m_presentationModel)) {
591 // If we know there is enough rows, always use max-height, we don't need to calculate size-hints
592 baseHeight = maxBaseHeight;
593 } else {
594 // Calculate size-hints to determine the best height
595 for (int row = 0; row < m_presentationModel->rowCount(); ++row) {
596 baseHeight += treeView()->sizeHintForRow(row);
597
598 QModelIndex index(m_presentationModel->index(row, 0));
599 if (index.isValid()) {
600 for (int row2 = 0; row2 < m_presentationModel->rowCount(index); ++row2) {
601 int h = 0;
602 for (int a = 0; a < m_presentationModel->columnCount(index); ++a) {
603 const QModelIndex child = m_presentationModel->index(row2, a, index);
604 int localHeight = treeView()->sizeHintForIndex(child).height();
605 if (localHeight > h) {
606 h = localHeight;
607 }
608 }
609 baseHeight += h;
610 if (baseHeight > maxBaseHeight) {
611 break;
612 }
613 }
614
615 if (baseHeight > maxBaseHeight) {
616 break;
617 }
618 }
619 }
620
621 calculatedCustomHeight = baseHeight;
622 }
623
624 baseHeight += 2 * frameWidth();
625
626 if (m_entryList->horizontalScrollBar()->isVisible()) {
627 baseHeight += m_entryList->horizontalScrollBar()->height();
628 }
629
630 if (baseHeight < minBaseHeight) {
631 baseHeight = minBaseHeight;
632 }
633 if (baseHeight > maxBaseHeight) {
634 baseHeight = maxBaseHeight;
636 } else {
637 // Somewhere there seems to be a bug that makes QTreeView add a scroll-bar
638 // even if the content exactly fits in. So forcefully disable the scroll-bar in that case
640 }
641
642 int newExpandingAddedHeight = 0;
643
644 if (baseHeight == maxBaseHeight) {
645 // Eventually add some more height
646 if (calculatedCustomHeight && calculatedCustomHeight > baseHeight && calculatedCustomHeight < maxBaseHeight) {
647 newExpandingAddedHeight = calculatedCustomHeight - baseHeight;
648 }
649 }
650
651 if (m_expandedAddedHeightBase != baseHeight && m_expandedAddedHeightBase - baseHeight > -2 && m_expandedAddedHeightBase - baseHeight < 2) {
652 // Re-use the stored base-height if it only slightly differs from the current one.
653 // Reason: Qt seems to apply slightly wrong sizes when the completion-widget is moved out of the screen at the bottom,
654 // which completely breaks this algorithm. Solution: re-use the old base-size if it only slightly differs from the computed one.
655 baseHeight = m_expandedAddedHeightBase;
656 }
657
658 int finalHeight = baseHeight + newExpandingAddedHeight;
659
660 if (finalHeight < 10) {
661 m_entryList->resize(m_entryList->width(), height() - 2 * frameWidth());
662 return;
663 }
664
665 m_expandedAddedHeightBase = geometry().height();
666
667 geom.setHeight(finalHeight);
668
669 // Work around a crash deep within the Qt 4.5 raster engine
670 m_entryList->setScrollingEnabled(false);
671
672 if (geometry() != geom) {
673 setGeometry(geom);
674 }
675
676 QSize entryListSize = QSize(m_entryList->width(), finalHeight - 2 * frameWidth());
677 if (m_entryList->size() != entryListSize) {
678 m_entryList->resize(entryListSize);
679 }
680
681 m_entryList->setScrollingEnabled(true);
682}
683
684void KateCompletionWidget::cursorPositionChanged()
685{
686 ////qCDebug(LOG_KTE);
687 if (m_completionRanges.isEmpty()) {
688 return;
689 }
690
691 QModelIndex oldCurrentSourceIndex;
692 if (m_entryList->currentIndex().isValid()) {
693 oldCurrentSourceIndex = m_presentationModel->mapToSource(m_entryList->currentIndex());
694 }
695
697
698 disconnect(this->model(), &KateCompletionModel::layoutChanged, this, &KateCompletionWidget::modelContentChanged);
699 disconnect(this->model(), &KateCompletionModel::modelReset, this, &KateCompletionWidget::modelContentChanged);
700
701 // Check the models and eventually abort some
702 const QList<KTextEditor::CodeCompletionModel *> checkCompletionRanges = m_completionRanges.keys();
703 for (auto model : checkCompletionRanges) {
704 if (!m_completionRanges.contains(model)) {
705 continue;
706 }
707
708 // qCDebug(LOG_KTE)<<"range before _updateRange:"<< *range;
709
710 // this might invalidate the range, therefore re-check afterwards
711 KTextEditor::Range rangeTE = m_completionRanges[model].range->toRange();
712 KTextEditor::Range newRange = _updateRange(model, view(), rangeTE);
713 if (!m_completionRanges.contains(model)) {
714 continue;
715 }
716
717 // update value
718 m_completionRanges[model].range->setRange(newRange);
719
720 // qCDebug(LOG_KTE)<<"range after _updateRange:"<< *range;
721 QString currentCompletion = _filterString(model, view(), *m_completionRanges[model].range, view()->cursorPosition());
722 if (!m_completionRanges.contains(model)) {
723 continue;
724 }
725
726 // qCDebug(LOG_KTE)<<"after _filterString, currentCompletion="<< currentCompletion;
727 bool abort = _shouldAbortCompletion(model, view(), *m_completionRanges[model].range, currentCompletion);
728 if (!m_completionRanges.contains(model)) {
729 continue;
730 }
731
732 // qCDebug(LOG_KTE)<<"after _shouldAbortCompletion:abort="<<abort;
733 if (view()->cursorPosition() < m_completionRanges[model].leftBoundary) {
734 // qCDebug(LOG_KTE) << "aborting because of boundary:
735 // cursor:"<<view()->cursorPosition()<<"completion_Range_left_boundary:"<<m_completionRanges[*it].leftBoundary;
736 abort = true;
737 }
738
739 if (!m_completionRanges.contains(model)) {
740 continue;
741 }
742
743 if (abort) {
744 if (m_completionRanges.count() == 1) {
745 // last model - abort whole completion
746 abortCompletion();
747 return;
748 } else {
749 {
750 delete m_completionRanges[model].range;
751 // qCDebug(LOG_KTE)<<"removing completion range 3";
752 m_completionRanges.remove(model);
753 }
754
755 _aborted(model, view());
756 m_presentationModel->removeCompletionModel(model);
757 }
758 } else {
759 filterStringByModel[model] = currentCompletion;
760 }
761 }
762
763 connect(this->model(), &KateCompletionModel::layoutChanged, this, &KateCompletionWidget::modelContentChanged);
764 connect(this->model(), &KateCompletionModel::modelReset, this, &KateCompletionWidget::modelContentChanged);
765
766 m_presentationModel->setCurrentCompletion(filterStringByModel);
767
768 if (oldCurrentSourceIndex.isValid()) {
769 QModelIndex idx = m_presentationModel->mapFromSource(oldCurrentSourceIndex);
770 // We only want to reselect this if it is still the first item
771 if (idx.isValid() && idx.row() == 0) {
772 // qCDebug(LOG_KTE) << "setting" << idx;
773 m_entryList->setCurrentIndex(idx.sibling(idx.row(), 0));
774 // m_entryList->nextCompletion();
775 // m_entryList->previousCompletion();
776 } else {
777 // qCDebug(LOG_KTE) << "failed to map from source";
778 }
779 }
780
781 m_entryList->scheduleUpdate();
782}
783
784bool KateCompletionWidget::isCompletionActive() const
785{
786 return !m_completionRanges.isEmpty() && ((!isHidden() && isVisible()) || (!m_argumentHintWidget->isHidden() && m_argumentHintWidget->isVisible()));
787}
788
789void KateCompletionWidget::abortCompletion()
790{
791 // qCDebug(LOG_KTE) ;
792
793 m_isSuspended = false;
794
795 if (!docTip()->isHidden()) {
796 docTip()->hide();
797 }
798
799 bool wasActive = isCompletionActive();
800
801 clear();
802
803 if (!isHidden()) {
804 hide();
805 }
806
807 if (!m_argumentHintWidget->isHidden()) {
808 m_argumentHintWidget->hide();
809 }
810
811 if (wasActive) {
812 view()->sendCompletionAborted();
813 }
814}
815
816void KateCompletionWidget::clear()
817{
818 m_presentationModel->clearCompletionModels();
819 m_argumentHintModel->clear();
820 m_docTip->clearWidgets();
821
822 const auto keys = m_completionRanges.keys();
823 for (KTextEditor::CodeCompletionModel *model : keys) {
824 _aborted(model, view());
825 }
826
827 deleteCompletionRanges();
828}
829
830bool KateCompletionWidget::navigateAccept()
831{
832 m_hadCompletionNavigation = true;
833
834 if (currentEmbeddedWidget()) {
835 QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetAccept");
836 }
837
838 QModelIndex index = selectedIndex();
839 if (index.isValid()) {
841 return true;
842 }
843 return false;
844}
845
846bool KateCompletionWidget::execute()
847{
848 // qCDebug(LOG_KTE) ;
849
850 if (!isCompletionActive()) {
851 return false;
852 }
853
854 QModelIndex index = selectedIndex();
855
856 if (!index.isValid()) {
857 abortCompletion();
858 return false;
859 }
860
861 QModelIndex toExecute;
862
863 if (index.model() == m_presentationModel) {
864 toExecute = m_presentationModel->mapToSource(index);
865 } else {
866 toExecute = m_argumentHintModel->mapToSource(index);
867 }
868
869 if (!toExecute.isValid()) {
870 qCWarning(LOG_KTE) << "Could not map index" << m_entryList->selectionModel()->currentIndex() << "to source index.";
871 abortCompletion();
872 return false;
873 }
874
875 // encapsulate all editing as being from the code completion, and undo-able in one step.
876 view()->doc()->editStart();
877 m_completionEditRunning = true;
878
879 // create scoped pointer, to ensure deletion of cursor
880 std::unique_ptr<KTextEditor::MovingCursor> oldPos(view()->doc()->newMovingCursor(view()->cursorPosition(), KTextEditor::MovingCursor::StayOnInsert));
881
882 KTextEditor::CodeCompletionModel *model = static_cast<KTextEditor::CodeCompletionModel *>(const_cast<QAbstractItemModel *>(toExecute.model()));
883 Q_ASSERT(model);
884
885 Q_ASSERT(m_completionRanges.contains(model));
886
887 KTextEditor::Cursor start = m_completionRanges[model].range->start();
888
889 // Save the "tail"
890 QString tailStr = tailString();
891 std::unique_ptr<KTextEditor::MovingCursor> afterTailMCursor(view()->doc()->newMovingCursor(view()->cursorPosition()));
892 afterTailMCursor->move(tailStr.size());
893
894 // Handle completion for multi cursors
895 std::shared_ptr<QMetaObject::Connection> connection(new QMetaObject::Connection());
896 auto autoCompleteMulticursors = [connection, this](KTextEditor::Document *document, const KTextEditor::Range &range) {
897 disconnect(*connection);
898 const QString text = document->text(range);
899 if (text.isEmpty()) {
900 return;
901 }
902 const auto &multicursors = view()->secondaryCursors();
903 for (const auto &c : multicursors) {
904 const KTextEditor::Cursor pos = c.cursor();
905 KTextEditor::Range wordToReplace = view()->doc()->wordRangeAt(pos);
906 wordToReplace.setEnd(pos); // limit the word to the current cursor position
907 view()->doc()->replaceText(wordToReplace, text);
908 }
909 };
910 *connection = connect(view()->doc(), &KTextEditor::DocumentPrivate::textInsertedRange, this, autoCompleteMulticursors);
911
912 model->executeCompletionItem(view(), *m_completionRanges[model].range, toExecute);
913 // NOTE the CompletionRange is now removed from m_completionRanges
914
915 // There are situations where keeping the tail is beneficial, but with the "Remove tail on complete" option is enabled,
916 // the tail is removed. For these situations we convert the completion into two edits:
917 // 1) Insert the completion
918 // 2) Remove the tail
919 //
920 // When we encounter one of these situations we can just do _one_ undo to have the tail back.
921 //
922 // Technically the tail is already removed by "executeCompletionItem()", so before this call we save the possible tail
923 // and re-add the tail before we end the first grouped "edit". Then immediately after that we add a second edit that
924 // removes the tail again.
925 // NOTE: The ViInputMode makes assumptions about the edit actions in a completion and breaks if we insert extra
926 // edits here, so we just disable this feature for ViInputMode
927 if (!tailStr.isEmpty() && view()->viewInputMode() != KTextEditor::View::ViInputMode) {
928 KTextEditor::Cursor currentPos = view()->cursorPosition();
929 KTextEditor::Cursor afterPos = afterTailMCursor->toCursor();
930 // Re add the tail for a possible undo to bring the tail back
931 view()->document()->insertText(afterPos, tailStr);
932 view()->setCursorPosition(currentPos);
933 view()->doc()->editEnd();
934
935 // Now remove the tail in a separate edit
936 KTextEditor::Cursor endPos = afterPos;
937 endPos.setColumn(afterPos.column() + tailStr.size());
938 view()->doc()->editStart();
939 view()->document()->removeText(KTextEditor::Range(afterPos, endPos));
940 }
941
942 view()->doc()->editEnd();
943 m_completionEditRunning = false;
944
945 abortCompletion();
946
947 view()->sendCompletionExecuted(start, model, toExecute);
948
949 KTextEditor::Cursor newPos = view()->cursorPosition();
950
951 if (newPos > *oldPos) {
952 m_automaticInvocationAt = newPos;
953 m_automaticInvocationLine = view()->doc()->text(KTextEditor::Range(*oldPos, newPos));
954 // qCDebug(LOG_KTE) << "executed, starting automatic invocation with line" << m_automaticInvocationLine;
955 m_lastInsertionByUser = false;
956 m_automaticInvocationTimer->start();
957 }
958
959 return true;
960}
961
962void KateCompletionWidget::resizeEvent(QResizeEvent *event)
963{
965
966 // keep argument hint geometry in sync
967 if (m_argumentHintWidget->isVisible()) {
968 updateArgumentHintGeometry();
969 }
970}
971
972void KateCompletionWidget::moveEvent(QMoveEvent *event)
973{
975
976 // keep argument hint geometry in sync
977 if (m_argumentHintWidget->isVisible()) {
978 updateArgumentHintGeometry();
979 }
980}
981
982void KateCompletionWidget::showEvent(QShowEvent *event)
983{
984 m_isSuspended = false;
985
987
988 if (!m_dontShowArgumentHints && m_argumentHintModel->rowCount(QModelIndex()) != 0) {
989 m_argumentHintWidget->positionAndShow();
990 }
991}
992
993KTextEditor::MovingRange *KateCompletionWidget::completionRange(KTextEditor::CodeCompletionModel *model) const
994{
995 if (!model) {
996 if (m_completionRanges.isEmpty()) {
997 return nullptr;
998 }
999
1000 KTextEditor::MovingRange *ret = m_completionRanges.begin()->range;
1001
1002 for (const CompletionRange &range : m_completionRanges) {
1003 if (range.range->start() > ret->start()) {
1004 ret = range.range;
1005 }
1006 }
1007 return ret;
1008 }
1009 if (m_completionRanges.contains(model)) {
1010 return m_completionRanges[model].range;
1011 } else {
1012 return nullptr;
1013 }
1014}
1015
1017{
1018 return m_completionRanges;
1019}
1020
1021void KateCompletionWidget::modelReset()
1022{
1023 setUpdatesEnabled(false);
1024 m_entryList->setAnimated(false);
1025
1026 for (int row = 0; row < m_entryList->model()->rowCount(QModelIndex()); ++row) {
1027 QModelIndex index(m_entryList->model()->index(row, 0, QModelIndex()));
1028 if (!m_entryList->isExpanded(index)) {
1029 m_entryList->expand(index);
1030 }
1031 }
1032 setUpdatesEnabled(true);
1033}
1034
1035KateCompletionTree *KateCompletionWidget::treeView() const
1036{
1037 return m_entryList;
1038}
1039
1040QModelIndex KateCompletionWidget::selectedIndex() const
1041{
1042 if (!isCompletionActive()) {
1043 return QModelIndex();
1044 }
1045
1046 return m_entryList->currentIndex();
1047}
1048
1049bool KateCompletionWidget::navigateLeft()
1050{
1051 m_hadCompletionNavigation = true;
1052 if (currentEmbeddedWidget()) {
1053 QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetLeft");
1054 }
1055
1056 QModelIndex index = selectedIndex();
1057
1058 if (index.isValid()) {
1060
1061 return true;
1062 }
1063 return false;
1064}
1065
1067{
1068 m_hadCompletionNavigation = true;
1069 if (currentEmbeddedWidget()) { ///@todo post 4.2: Make these slots public interface, or create an interface using virtual functions
1070 QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetRight");
1071 }
1072
1073 QModelIndex index = selectedIndex();
1074
1075 if (index.isValid()) {
1077 return true;
1078 }
1079
1080 return false;
1081}
1082
1083bool KateCompletionWidget::navigateBack()
1084{
1085 m_hadCompletionNavigation = true;
1086 if (currentEmbeddedWidget()) {
1087 QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetBack");
1088 }
1089 return false;
1090}
1091
1092void KateCompletionWidget::toggleDocumentation()
1093{
1094 // user has configured the doc to be always visible
1095 // whenever its available.
1096 if (view()->config()->showDocWithCompletion()) {
1097 return;
1098 }
1099
1100 if (m_docTip->isVisible()) {
1101 m_hadCompletionNavigation = false;
1102 QTimer::singleShot(400, this, [this] {
1103 // if 400ms later this is not false, it means
1104 // that the user navigated inside the active
1105 // widget in doc tip
1106 if (!m_hadCompletionNavigation) {
1107 m_docTip->hide();
1108 }
1109 });
1110 } else {
1111 showDocTip(m_entryList->currentIndex());
1112 }
1113}
1114
1115void KateCompletionWidget::showDocTip(const QModelIndex &idx)
1116{
1118 // No data => hide
1119 if (!data.isValid()) {
1120 m_docTip->hide();
1121 return;
1122 } else if (data.canConvert<QWidget *>()) {
1123 m_docTip->setWidget(data.value<QWidget *>());
1124 } else if (data.canConvert<QString>()) {
1125 QString text = data.toString();
1126 if (text.isEmpty()) {
1127 m_docTip->hide();
1128 return;
1129 }
1130 m_docTip->setText(text);
1131 }
1132
1133 m_docTip->updatePosition(this);
1134 if (!m_docTip->isVisible()) {
1135 m_docTip->show();
1136 }
1137}
1138
1139bool KateCompletionWidget::eventFilter(QObject *watched, QEvent *event)
1140{
1141 if (watched != this && event->type() == QEvent::Resize && isCompletionActive()) {
1142 abortCompletion();
1143 } else if (event->type() == QEvent::KeyRelease && isCompletionActive()) {
1144 auto e = static_cast<QKeyEvent *>(event);
1145 if (e->key() == Qt::Key_Left && e->modifiers() == Qt::AltModifier) {
1146 if (navigateLeft()) {
1147 return true;
1148 }
1149 }
1150 if (e->key() == Qt::Key_Right && e->modifiers() == Qt::AltModifier) {
1151 if (navigateRight()) {
1152 return true;
1153 }
1154 }
1155 if (e->key() == Qt::Key_Up && e->modifiers() == Qt::AltModifier) {
1156 if (navigateUp()) {
1157 return true;
1158 }
1159 }
1160 if (e->key() == Qt::Key_Down && e->modifiers() == Qt::AltModifier) {
1161 if (navigateDown()) {
1162 return true;
1163 }
1164 }
1165 if (e->key() == Qt::Key_Return && e->modifiers() == Qt::AltModifier) {
1166 if (navigateAccept()) {
1167 return true;
1168 }
1169 }
1170 if (e->key() == Qt::Key_Backspace && e->modifiers() == Qt::AltModifier) {
1171 if (navigateBack()) {
1172 return true;
1173 }
1174 }
1175 }
1176 return QFrame::eventFilter(watched, event);
1177}
1178
1179bool KateCompletionWidget::navigateDown()
1180{
1181 m_hadCompletionNavigation = true;
1182 if (m_argumentHintModel->rowCount() > 0) {
1183 m_argumentHintWidget->selectNext();
1184 return true;
1185 } else if (currentEmbeddedWidget()) {
1186 QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetDown");
1187 }
1188 return false;
1189}
1190
1191bool KateCompletionWidget::navigateUp()
1192{
1193 m_hadCompletionNavigation = true;
1194 if (m_argumentHintModel->rowCount() > 0) {
1195 m_argumentHintWidget->selectPrevious();
1196 return true;
1197 } else if (currentEmbeddedWidget()) {
1198 QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetUp");
1199 }
1200 return false;
1201}
1202
1203QWidget *KateCompletionWidget::currentEmbeddedWidget()
1204{
1205 return m_docTip->currentWidget();
1206}
1207
1208void KateCompletionWidget::cursorDown()
1209{
1210 m_entryList->nextCompletion();
1211}
1212
1213void KateCompletionWidget::cursorUp()
1214{
1215 m_entryList->previousCompletion();
1216}
1217
1218void KateCompletionWidget::pageDown()
1219{
1220 m_entryList->pageDown();
1221}
1222
1223void KateCompletionWidget::pageUp()
1224{
1225 m_entryList->pageUp();
1226}
1227
1228void KateCompletionWidget::top()
1229{
1230 m_entryList->top();
1231}
1232
1233void KateCompletionWidget::bottom()
1234{
1235 m_entryList->bottom();
1236}
1237
1238void KateCompletionWidget::completionModelReset()
1239{
1240 KTextEditor::CodeCompletionModel *model = qobject_cast<KTextEditor::CodeCompletionModel *>(sender());
1241 if (!model) {
1242 qCWarning(LOG_KTE) << "bad sender";
1243 return;
1244 }
1245
1246 if (!m_waitingForReset.contains(model)) {
1247 return;
1248 }
1249
1250 m_waitingForReset.remove(model);
1251
1252 if (m_waitingForReset.isEmpty()) {
1253 if (!isCompletionActive()) {
1254 // qCDebug(LOG_KTE) << "all completion-models we waited for are ready. Last one: " << model->objectName();
1255 // Eventually show the completion-list if this was the last model we were waiting for
1256 // Use a queued connection once again to make sure that KateCompletionModel is notified before we are
1257 QMetaObject::invokeMethod(this, "modelContentChanged", Qt::QueuedConnection);
1258 }
1259 }
1260}
1261
1262void KateCompletionWidget::modelDestroyed(QObject *model)
1263{
1264 m_sourceModels.removeAll(model);
1265 abortCompletion();
1266}
1267
1268void KateCompletionWidget::registerCompletionModel(KTextEditor::CodeCompletionModel *model)
1269{
1270 if (m_sourceModels.contains(model)) {
1271 return;
1272 }
1273
1274 connect(model, &KTextEditor::CodeCompletionModel::destroyed, this, &KateCompletionWidget::modelDestroyed);
1275 // This connection must not be queued
1276 connect(model, &KTextEditor::CodeCompletionModel::modelReset, this, &KateCompletionWidget::completionModelReset);
1277
1278 m_sourceModels.append(model);
1279
1280 if (isCompletionActive()) {
1281 m_presentationModel->addCompletionModel(model);
1282 }
1283}
1284
1285void KateCompletionWidget::unregisterCompletionModel(KTextEditor::CodeCompletionModel *model)
1286{
1287 disconnect(model, &KTextEditor::CodeCompletionModel::destroyed, this, &KateCompletionWidget::modelDestroyed);
1288 disconnect(model, &KTextEditor::CodeCompletionModel::modelReset, this, &KateCompletionWidget::completionModelReset);
1289
1290 m_sourceModels.removeAll(model);
1291 abortCompletion();
1292}
1293
1294bool KateCompletionWidget::isCompletionModelRegistered(KTextEditor::CodeCompletionModel *model) const
1295{
1296 return m_sourceModels.contains(model);
1297}
1298
1299QList<KTextEditor::CodeCompletionModel *> KateCompletionWidget::codeCompletionModels() const
1300{
1301 return m_sourceModels;
1302}
1303
1304int KateCompletionWidget::automaticInvocationDelay() const
1305{
1306 return m_automaticInvocationDelay;
1307}
1308
1309void KateCompletionWidget::setIgnoreBufferSignals(bool ignore) const
1310{
1311 if (ignore) {
1312 disconnect(view()->doc(), &KTextEditor::Document::lineWrapped, this, &KateCompletionWidget::wrapLine);
1313 disconnect(view()->doc(), &KTextEditor::Document::lineUnwrapped, this, &KateCompletionWidget::unwrapLine);
1314 disconnect(view()->doc(), &KTextEditor::Document::textInserted, this, &KateCompletionWidget::insertText);
1315 disconnect(view()->doc(), &KTextEditor::Document::textRemoved, this, &KateCompletionWidget::removeText);
1316 } else {
1317 connect(view()->doc(), &KTextEditor::Document::lineWrapped, this, &KateCompletionWidget::wrapLine);
1318 connect(view()->doc(), &KTextEditor::Document::lineUnwrapped, this, &KateCompletionWidget::unwrapLine);
1319 connect(view()->doc(), &KTextEditor::Document::textInserted, this, &KateCompletionWidget::insertText);
1320 connect(view()->doc(), &KTextEditor::Document::textRemoved, this, &KateCompletionWidget::removeText);
1321 }
1322}
1323
1324void KateCompletionWidget::setAutomaticInvocationDelay(int delay)
1325{
1326 m_automaticInvocationDelay = delay;
1327}
1328
1329void KateCompletionWidget::wrapLine(KTextEditor::Document *, KTextEditor::Cursor)
1330{
1331 m_lastInsertionByUser = !m_completionEditRunning;
1332
1333 // wrap line, be done
1334 m_automaticInvocationLine.clear();
1335 m_automaticInvocationTimer->stop();
1336}
1337
1338void KateCompletionWidget::unwrapLine(KTextEditor::Document *, int)
1339{
1340 m_lastInsertionByUser = !m_completionEditRunning;
1341
1342 // just removal
1343 m_automaticInvocationLine.clear();
1344 m_automaticInvocationTimer->stop();
1345}
1346
1347void KateCompletionWidget::insertText(KTextEditor::Document *, KTextEditor::Cursor position, const QString &text)
1348{
1349 m_lastInsertionByUser = !m_completionEditRunning;
1350
1351 // no invoke?
1352 if (!view()->isAutomaticInvocationEnabled()) {
1353 m_automaticInvocationLine.clear();
1354 m_automaticInvocationTimer->stop();
1355 return;
1356 }
1357
1358 if (m_automaticInvocationAt != position) {
1359 m_automaticInvocationLine.clear();
1360 m_lastInsertionByUser = !m_completionEditRunning;
1361 }
1362
1363 m_automaticInvocationLine += text;
1364 m_automaticInvocationAt = position;
1365 m_automaticInvocationAt.setColumn(position.column() + text.length());
1366
1367 if (m_automaticInvocationLine.isEmpty()) {
1368 m_automaticInvocationTimer->stop();
1369 return;
1370 }
1371
1372 m_automaticInvocationTimer->start(m_automaticInvocationDelay);
1373}
1374
1375void KateCompletionWidget::removeText(KTextEditor::Document *, KTextEditor::Range, const QString &)
1376{
1377 m_lastInsertionByUser = !m_completionEditRunning;
1378
1379 // just removal
1380 m_automaticInvocationLine.clear();
1381 m_automaticInvocationTimer->stop();
1382}
1383
1384void KateCompletionWidget::automaticInvocation()
1385{
1386 // qCDebug(LOG_KTE)<<"m_automaticInvocationAt:"<<m_automaticInvocationAt;
1387 // qCDebug(LOG_KTE)<<view()->cursorPosition();
1388 if (m_automaticInvocationAt != view()->cursorPosition()) {
1389 return;
1390 }
1391
1392 bool start = false;
1394
1395 // qCDebug(LOG_KTE)<<"checking models";
1396 for (KTextEditor::CodeCompletionModel *model : std::as_const(m_sourceModels)) {
1397 // qCDebug(LOG_KTE)<<"m_completionRanges contains model?:"<<m_completionRanges.contains(model);
1398 if (m_completionRanges.contains(model)) {
1399 continue;
1400 }
1401
1402 start = _shouldStartCompletion(model, view(), m_automaticInvocationLine, m_lastInsertionByUser, view()->cursorPosition());
1403 // qCDebug(LOG_KTE)<<"start="<<start;
1404 if (start) {
1405 models << model;
1406 }
1407 }
1408 // qCDebug(LOG_KTE)<<"models found:"<<!models.isEmpty();
1409 if (!models.isEmpty()) {
1410 // Start automatic code completion
1411 startCompletion(KTextEditor::CodeCompletionModel::AutomaticInvocation, models);
1412 }
1413}
1414
1415void KateCompletionWidget::userInvokedCompletion()
1416{
1417 startCompletion(KTextEditor::CodeCompletionModel::UserInvocation);
1418}
1419
1420void KateCompletionWidget::tabCompletion(Direction direction)
1421{
1422 m_noAutoHide = true;
1423
1424 // Not using cursorDown/Up() as we don't want to go into the argument-hint list
1425 if (direction == Down) {
1426 const bool res = m_entryList->nextCompletion();
1427 if (!res) {
1428 m_entryList->top();
1429 }
1430 } else { // direction == Up
1431 const bool res = m_entryList->previousCompletion();
1432 if (!res) {
1433 m_entryList->bottom();
1434 }
1435 }
1436}
1437
1438#include "moc_katecompletionwidget.cpp"
An item model for providing code completion, and meta information for enhanced presentation.
int rowCount(const QModelIndex &parent=QModelIndex()) const override
Reimplemented from QAbstractItemModel::rowCount().
virtual void completionInvoked(KTextEditor::View *view, const KTextEditor::Range &range, InvocationType invocationType)
This function is responsible to generating / updating the list of current completions.
void waitForReset()
Emit this if the code-completion for this model was invoked, some time is needed in order to get the ...
@ AccessibilityAccept
AccessibilityAccept will be requested on an item if it is expanded, contains an expanding-widget,...
@ ExpandingWidget
After a model returned true for a row on IsExpandable, the row may be expanded by the user.
@ AccessibilityNext
The following three enumeration-values are only used on expanded completion-list items that contain a...
@ AccessibilityPrevious
AccessibilityPrevious will be requested on an item if it is expanded, contains an expanding-widget,...
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
Reimplemented from QAbstractItemModel::index().
virtual void executeCompletionItem(KTextEditor::View *view, const Range &word, const QModelIndex &index) const
This function is responsible for inserting a selected completion into the view.
The Cursor represents a position in a Document.
Definition cursor.h:75
constexpr int column() const noexcept
Retrieve the column on which this cursor is situated.
Definition cursor.h:192
void setColumn(int column) noexcept
Set the cursor column to column.
Definition cursor.h:201
constexpr int line() const noexcept
Retrieve the line on which this cursor is situated.
Definition cursor.h:174
A KParts derived class representing a text document.
Definition document.h:284
virtual QString line(int line) const =0
Get a single text line.
void lineUnwrapped(KTextEditor::Document *document, int line)
A line got unwrapped.
virtual bool removeText(Range range, bool block=false)=0
Remove the text specified in range.
void lineWrapped(KTextEditor::Document *document, KTextEditor::Cursor position)
A line got wrapped.
virtual bool insertText(KTextEditor::Cursor position, const QString &text, bool block=false)=0
Insert text at position.
void textInserted(KTextEditor::Document *document, KTextEditor::Cursor position, const QString &text)
Text got inserted.
void textRemoved(KTextEditor::Document *document, KTextEditor::Range range, const QString &text)
Text got removed.
KTextEditor::MainWindow * dummyMainWindow()
Dummy main window to be null safe.
Definition kateglobal.h:387
static KTextEditor::EditorPrivate * self()
Kate Part Internal stuff ;)
QWidget * window()
Get the toplevel widget.
@ StayOnInsert
stay on insert
A range that is bound to a specific Document, and maintains its position.
virtual const MovingCursor & start() const =0
Retrieve start cursor of this range, read-only.
@ ExpandRight
Expand to encapsulate new characters to the right of the range.
@ ExpandLeft
Expand to encapsulate new characters to the left of the range.
An object representing a section of text, from one Cursor to another.
constexpr Cursor start() const noexcept
Get the start position of this range.
void setEnd(Cursor end) noexcept
Set the end cursor to end.
constexpr bool isValid() const noexcept
Validity check.
A text widget with KXMLGUIClient that represents a Document.
Definition view.h:244
virtual bool setCursorPosition(Cursor position)=0
Set the view's new cursor to position.
virtual Document * document() const =0
Get the view's document, that means the view is a view of the returned document.
void focusOut(KTextEditor::View *view)
This signal is emitted whenever the view loses the focus.
virtual Cursor cursorPosition() const =0
Get the view's current cursor position.
@ ViInputMode
Vi mode.
Definition view.h:288
void cursorPositionChanged(KTextEditor::View *view, KTextEditor::Cursor newPosition)
This signal is emitted whenever the view's cursor position changed.
virtual QPoint cursorToCoordinate(KTextEditor::Cursor cursor) const =0
Get the screen coordinates (x, y) of the supplied cursor relative to the view widget in pixels.
void verticalScrollPositionChanged(KTextEditor::View *view, KTextEditor::Cursor newPos)
This signal should be emitted whenever the view is scrolled vertically.
This class has the responsibility for filtering, sorting, and manipulating code completion data provi...
virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const
Maps from this display-model into the appropriate source code-completion model.
bool shouldMatchHideCompletionList() const
Returns whether one of the filtered items exactly matches its completion string.
virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const
Maps from an index in a source-model to the index of the item in this display-model.
void updateHeight()
Called by KateViewInternal, because we need the specific information from the event.
Q_SCRIPTABLE Q_NOREPLY void abort()
Q_SCRIPTABLE Q_NOREPLY void start()
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
const QList< QKeySequence > & end()
The KTextEditor namespace contains all the public API that is required to use the KTextEditor compone...
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const=0
void layoutChanged(const QList< QPersistentModelIndex > &parents, QAbstractItemModel::LayoutChangeHint hint)
virtual int rowCount(const QModelIndex &parent) const const=0
void rowsInserted(const QModelIndex &parent, int first, int last)
QModelIndex currentIndex() const const
void doubleClicked(const QModelIndex &index)
QAbstractItemModel * model() const const
QItemSelectionModel * selectionModel() const const
void setCurrentIndex(const QModelIndex &index)
QSize sizeHintForIndex(const QModelIndex &index) const const
virtual int sizeHintForRow(int row) const const
QScrollBar * horizontalScrollBar() const const
void setVerticalScrollBarPolicy(Qt::ScrollBarPolicy)
QWidget * activeWindow()
QWidget * focusWidget()
virtual bool event(QEvent *e) override
QModelIndex currentIndex() const const
void append(const T &value)
bool contains(const T &value) const const
bool isEmpty() const const
int removeAll(const T &value)
QMap::iterator begin()
void clear()
bool contains(const Key &key) const const
int count(const Key &key) const const
bool isEmpty() const const
QMap::key_iterator keyBegin() const const
QMap::key_iterator keyEnd() const const
QList< Key > keys() const const
int remove(const Key &key)
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
QVariant data(int role) const const
bool isValid() const const
const QAbstractItemModel * model() const const
int row() const const
QModelIndex sibling(int row, int column) const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void destroyed(QObject *obj)
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
virtual bool eventFilter(QObject *watched, QEvent *event)
QObject * parent() const const
QObject * sender() const const
int x() const const
int y() const const
void setHeight(int height)
bool contains(const T &value) const const
QSet::iterator insert(const T &value)
bool isEmpty() const const
bool remove(const T &value)
int height() const const
void clear()
bool isEmpty() const const
int length() const const
QString mid(int position, int n) const const
int size() const const
QueuedConnection
ClickFocus
Key_Left
AltModifier
ScrollBarAsNeeded
void start(int msec)
void stop()
void timeout()
void setAnimated(bool enable)
void expand(const QModelIndex &index)
bool isExpanded(const QModelIndex &index) const const
bool hasFocus() const const
void hide()
bool isAncestorOf(const QWidget *child) const const
bool isHidden() const const
QPoint mapFromGlobal(const QPoint &pos) const const
QPoint mapToGlobal(const QPoint &pos) const const
void move(int x, int y)
virtual void moveEvent(QMoveEvent *event)
QWidget * parentWidget() const const
void resize(int w, int h)
virtual void resizeEvent(QResizeEvent *event)
void setGeometry(int x, int y, int w, int h)
void show()
virtual void showEvent(QShowEvent *event)
void setUpdatesEnabled(bool enable)
bool isVisible() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Feb 24 2024 20:00:58 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.