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

KDE's Doxygen guidelines are available online.