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

KDE's Doxygen guidelines are available online.