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
521 QPoint localCursorCoord = view()->cursorToCoordinate(completionRange()->start());
522 int s = m_entryList->textColumnOffset() + (frameWidth() * 2);
523 localCursorCoord.rx() -= s;
524 if (localCursorCoord == QPoint(-1, -1)) {
525 // Start of completion range is now off-screen -> abort
526 abortCompletion();
527 return;
528 }
529
530 const QPoint cursorCoordinate = view()->mapToGlobal(localCursorCoord);
531 QPoint p = cursorCoordinate;
532 int x = p.x();
533 int y = p.y();
534
535 y += view()->renderer()->currentFontMetrics().height() + 2;
536
537 const auto windowGeometry = parentWidget()->geometry();
538 if (x + width() > windowGeometry.right()) {
539 // crossing right edge
540 x = windowGeometry.right() - width();
541 }
542 if (x < windowGeometry.left()) {
543 x = windowGeometry.left();
544 }
545
546 if (y + height() > windowGeometry.bottom()) {
547 // move above cursor if we are crossing the bottom
548 y -= height();
549 if (y + height() > cursorCoordinate.y()) {
550 y -= (y + height()) - cursorCoordinate.y();
551 y -= 2;
552 }
553 }
554
556}
557
558void KateCompletionWidget::updateArgumentHintGeometry()
559{
560 if (!m_dontShowArgumentHints) {
561 // Now place the argument-hint widget
562 m_argumentHintWidget->updateGeometry();
563 }
564}
565
566// Checks whether the given model has at least "rows" rows, also searching the second level of the tree.
567static bool hasAtLeastNRows(int rows, QAbstractItemModel *model)
568{
569 int count = 0;
570 const auto rowCount = model->rowCount();
571 for (int row = 0; row < rowCount; ++row) {
572 ++count;
573
574 QModelIndex index(model->index(row, 0));
575 if (index.isValid()) {
576 count += model->rowCount(index);
577 }
578
579 if (count > rows) {
580 return true;
581 }
582 }
583 return false;
584}
585
587{
588 QRect geom = geometry();
589
590 constexpr int minBaseHeight = 10;
591 constexpr int maxBaseHeight = 300;
592
593 int baseHeight = 0;
594 int calculatedCustomHeight = 0;
595
596 if (hasAtLeastNRows(15, m_presentationModel)) {
597 // If we know there is enough rows, always use max-height, we don't need to calculate size-hints
598 baseHeight = maxBaseHeight;
599 } else {
600 // Calculate size-hints to determine the best height
601 for (int row = 0; row < m_presentationModel->rowCount(); ++row) {
602 baseHeight += treeView()->sizeHintForRow(row);
603
604 QModelIndex index(m_presentationModel->index(row, 0));
605 if (index.isValid()) {
606 for (int row2 = 0; row2 < m_presentationModel->rowCount(index); ++row2) {
607 int h = 0;
608 for (int a = 0; a < m_presentationModel->columnCount(index); ++a) {
609 const QModelIndex child = m_presentationModel->index(row2, a, index);
610 int localHeight = treeView()->sizeHintForIndex(child).height();
611 if (localHeight > h) {
612 h = localHeight;
613 }
614 }
615 baseHeight += h;
616 if (baseHeight > maxBaseHeight) {
617 break;
618 }
619 }
620
621 if (baseHeight > maxBaseHeight) {
622 break;
623 }
624 }
625 }
626
627 calculatedCustomHeight = baseHeight;
628 }
629
630 baseHeight += 2 * frameWidth();
631
632 if (m_entryList->horizontalScrollBar()->isVisible()) {
633 baseHeight += m_entryList->horizontalScrollBar()->height();
634 }
635
636 if (baseHeight < minBaseHeight) {
637 baseHeight = minBaseHeight;
638 }
639 if (baseHeight > maxBaseHeight) {
640 baseHeight = maxBaseHeight;
642 } else {
643 // Somewhere there seems to be a bug that makes QTreeView add a scroll-bar
644 // even if the content exactly fits in. So forcefully disable the scroll-bar in that case
646 }
647
648 int newExpandingAddedHeight = 0;
649
650 if (baseHeight == maxBaseHeight) {
651 // Eventually add some more height
652 if (calculatedCustomHeight && calculatedCustomHeight > baseHeight && calculatedCustomHeight < maxBaseHeight) {
653 newExpandingAddedHeight = calculatedCustomHeight - baseHeight;
654 }
655 }
656
657 if (m_expandedAddedHeightBase != baseHeight && m_expandedAddedHeightBase - baseHeight > -2 && m_expandedAddedHeightBase - baseHeight < 2) {
658 // Re-use the stored base-height if it only slightly differs from the current one.
659 // Reason: Qt seems to apply slightly wrong sizes when the completion-widget is moved out of the screen at the bottom,
660 // which completely breaks this algorithm. Solution: re-use the old base-size if it only slightly differs from the computed one.
661 baseHeight = m_expandedAddedHeightBase;
662 }
663
664 int finalHeight = baseHeight + newExpandingAddedHeight;
665
666 if (finalHeight < 10) {
667 m_entryList->resize(m_entryList->width(), height() - 2 * frameWidth());
668 return;
669 }
670
671 m_expandedAddedHeightBase = geometry().height();
672
673 geom.setHeight(finalHeight);
674
675 // Work around a crash deep within the Qt 4.5 raster engine
676 m_entryList->setScrollingEnabled(false);
677
678 if (geometry() != geom) {
679 setGeometry(geom);
680 }
681
682 QSize entryListSize = QSize(m_entryList->width(), finalHeight - 2 * frameWidth());
683 if (m_entryList->size() != entryListSize) {
684 m_entryList->resize(entryListSize);
685 }
686
687 m_entryList->setScrollingEnabled(true);
688}
689
690void KateCompletionWidget::cursorPositionChanged()
691{
692 ////qCDebug(LOG_KTE);
693 if (m_completionRanges.isEmpty()) {
694 return;
695 }
696
697 QModelIndex oldCurrentSourceIndex;
698 if (m_entryList->currentIndex().isValid()) {
699 oldCurrentSourceIndex = m_presentationModel->mapToSource(m_entryList->currentIndex());
700 }
701
703
704 disconnect(this->model(), &KateCompletionModel::layoutChanged, this, &KateCompletionWidget::modelContentChanged);
705 disconnect(this->model(), &KateCompletionModel::modelReset, this, &KateCompletionWidget::modelContentChanged);
706
707 // Check the models and eventually abort some
708 const QList<KTextEditor::CodeCompletionModel *> checkCompletionRanges = m_completionRanges.keys();
709 for (auto model : checkCompletionRanges) {
710 if (!m_completionRanges.contains(model)) {
711 continue;
712 }
713
714 // qCDebug(LOG_KTE)<<"range before _updateRange:"<< *range;
715
716 // this might invalidate the range, therefore re-check afterwards
717 KTextEditor::Range rangeTE = m_completionRanges[model].range->toRange();
718 KTextEditor::Range newRange = _updateRange(model, view(), rangeTE);
719 if (!m_completionRanges.contains(model)) {
720 continue;
721 }
722
723 // update value
724 m_completionRanges[model].range->setRange(newRange);
725
726 // qCDebug(LOG_KTE)<<"range after _updateRange:"<< *range;
727 QString currentCompletion = _filterString(model, view(), *m_completionRanges[model].range, view()->cursorPosition());
728 if (!m_completionRanges.contains(model)) {
729 continue;
730 }
731
732 // qCDebug(LOG_KTE)<<"after _filterString, currentCompletion="<< currentCompletion;
733 bool abort = _shouldAbortCompletion(model, view(), *m_completionRanges[model].range, currentCompletion);
734 if (!m_completionRanges.contains(model)) {
735 continue;
736 }
737
738 // qCDebug(LOG_KTE)<<"after _shouldAbortCompletion:abort="<<abort;
739 if (view()->cursorPosition() < m_completionRanges[model].leftBoundary) {
740 // qCDebug(LOG_KTE) << "aborting because of boundary:
741 // cursor:"<<view()->cursorPosition()<<"completion_Range_left_boundary:"<<m_completionRanges[*it].leftBoundary;
742 abort = true;
743 }
744
745 if (!m_completionRanges.contains(model)) {
746 continue;
747 }
748
749 if (abort) {
750 if (m_completionRanges.count() == 1) {
751 // last model - abort whole completion
752 abortCompletion();
753 return;
754 } else {
755 {
756 delete m_completionRanges[model].range;
757 // qCDebug(LOG_KTE)<<"removing completion range 3";
758 m_completionRanges.remove(model);
759 }
760
761 _aborted(model, view());
762 m_presentationModel->removeCompletionModel(model);
763 }
764 } else {
765 filterStringByModel[model] = currentCompletion;
766 }
767 }
768
769 connect(this->model(), &KateCompletionModel::layoutChanged, this, &KateCompletionWidget::modelContentChanged);
770 connect(this->model(), &KateCompletionModel::modelReset, this, &KateCompletionWidget::modelContentChanged);
771
772 m_presentationModel->setCurrentCompletion(filterStringByModel);
773
774 if (oldCurrentSourceIndex.isValid()) {
775 QModelIndex idx = m_presentationModel->mapFromSource(oldCurrentSourceIndex);
776 // We only want to reselect this if it is still the first item
777 if (idx.isValid() && idx.row() == 0) {
778 // qCDebug(LOG_KTE) << "setting" << idx;
779 m_entryList->setCurrentIndex(idx.sibling(idx.row(), 0));
780 // m_entryList->nextCompletion();
781 // m_entryList->previousCompletion();
782 } else {
783 // qCDebug(LOG_KTE) << "failed to map from source";
784 }
785 }
786
787 m_entryList->scheduleUpdate();
788}
789
790bool KateCompletionWidget::isCompletionActive() const
791{
792 return !m_completionRanges.isEmpty() && ((!isHidden() && isVisible()) || (!m_argumentHintWidget->isHidden() && m_argumentHintWidget->isVisible()));
793}
794
795void KateCompletionWidget::abortCompletion()
796{
797 // qCDebug(LOG_KTE) ;
798
799 m_isSuspended = false;
800
801 if (!docTip()->isHidden()) {
802 docTip()->hide();
803 }
804
805 bool wasActive = isCompletionActive();
806
807 clear();
808
809 if (!isHidden()) {
810 hide();
811 }
812
813 if (!m_argumentHintWidget->isHidden()) {
814 m_argumentHintWidget->hide();
815 }
816
817 if (wasActive) {
818 view()->sendCompletionAborted();
819 }
820}
821
822void KateCompletionWidget::clear()
823{
824 m_presentationModel->clearCompletionModels();
825 m_argumentHintModel->clear();
826 m_docTip->clearWidgets();
827
828 const auto keys = m_completionRanges.keys();
829 for (KTextEditor::CodeCompletionModel *model : keys) {
830 _aborted(model, view());
831 }
832
833 deleteCompletionRanges();
834}
835
836bool KateCompletionWidget::navigateAccept()
837{
838 m_hadCompletionNavigation = true;
839
840 if (currentEmbeddedWidget()) {
841 QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetAccept");
842 }
843
844 QModelIndex index = selectedIndex();
845 if (index.isValid()) {
847 return true;
848 }
849 return false;
850}
851
852bool KateCompletionWidget::execute()
853{
854 // qCDebug(LOG_KTE) ;
855
856 if (!isCompletionActive()) {
857 return false;
858 }
859
860 // dont auto complete before 250ms to avoid unwanted completions with auto invocation
861 const auto cursorPosition = view()->cursorPosition();
862 const int len = view()->doc()->lineLength(cursorPosition.line());
863 const bool atEndOfLine = cursorPosition.column() >= len;
864 if (atEndOfLine && m_lastInvocationType == KTextEditor::CodeCompletionModel::AutomaticInvocation
865 && m_timeSinceShowing.elapsed() < minRequiredMsToAcceptCompletion()) {
866 return false;
867 }
868
869 QModelIndex index = selectedIndex();
870
871 if (!index.isValid()) {
872 abortCompletion();
873 return false;
874 }
875
876 QModelIndex toExecute;
877
878 if (index.model() == m_presentationModel) {
879 toExecute = m_presentationModel->mapToSource(index);
880 } else {
881 toExecute = m_argumentHintModel->mapToSource(index);
882 }
883
884 if (!toExecute.isValid()) {
885 qCWarning(LOG_KTE) << "Could not map index" << m_entryList->selectionModel()->currentIndex() << "to source index.";
886 abortCompletion();
887 return false;
888 }
889
890 // encapsulate all editing as being from the code completion, and undo-able in one step.
891 view()->doc()->editStart();
892 m_completionEditRunning = true;
893
894 // create scoped pointer, to ensure deletion of cursor
895 std::unique_ptr<KTextEditor::MovingCursor> oldPos(view()->doc()->newMovingCursor(view()->cursorPosition(), KTextEditor::MovingCursor::StayOnInsert));
896
897 KTextEditor::CodeCompletionModel *model = static_cast<KTextEditor::CodeCompletionModel *>(const_cast<QAbstractItemModel *>(toExecute.model()));
898 Q_ASSERT(model);
899
900 Q_ASSERT(m_completionRanges.contains(model));
901
902 KTextEditor::Cursor start = m_completionRanges[model].range->start();
903
904 // Save the "tail"
905 QString tailStr = tailString();
906 std::unique_ptr<KTextEditor::MovingCursor> afterTailMCursor(view()->doc()->newMovingCursor(view()->cursorPosition()));
907 afterTailMCursor->move(tailStr.size());
908
909 // Handle completion for multi cursors
910 std::shared_ptr<QMetaObject::Connection> connection(new QMetaObject::Connection());
911 auto autoCompleteMulticursors = [connection, this](KTextEditor::Document *document, const KTextEditor::Range &range) {
912 disconnect(*connection);
913 const QString text = document->text(range);
914 if (text.isEmpty()) {
915 return;
916 }
917 const auto &multicursors = view()->secondaryCursors();
918 for (const auto &c : multicursors) {
919 const KTextEditor::Cursor pos = c.cursor();
920 KTextEditor::Range wordToReplace = view()->doc()->wordRangeAt(pos);
921 wordToReplace.setEnd(pos); // limit the word to the current cursor position
922 view()->doc()->replaceText(wordToReplace, text);
923 }
924 };
925 *connection = connect(view()->doc(), &KTextEditor::DocumentPrivate::textInsertedRange, this, autoCompleteMulticursors);
926
927 model->executeCompletionItem(view(), *m_completionRanges[model].range, toExecute);
928 // NOTE the CompletionRange is now removed from m_completionRanges
929
930 // There are situations where keeping the tail is beneficial, but with the "Remove tail on complete" option is enabled,
931 // the tail is removed. For these situations we convert the completion into two edits:
932 // 1) Insert the completion
933 // 2) Remove the tail
934 //
935 // When we encounter one of these situations we can just do _one_ undo to have the tail back.
936 //
937 // Technically the tail is already removed by "executeCompletionItem()", so before this call we save the possible tail
938 // and re-add the tail before we end the first grouped "edit". Then immediately after that we add a second edit that
939 // removes the tail again.
940 // NOTE: The ViInputMode makes assumptions about the edit actions in a completion and breaks if we insert extra
941 // edits here, so we just disable this feature for ViInputMode
942 if (!tailStr.isEmpty() && view()->viewInputMode() != KTextEditor::View::ViInputMode) {
943 KTextEditor::Cursor currentPos = view()->cursorPosition();
944 KTextEditor::Cursor afterPos = afterTailMCursor->toCursor();
945 // Re add the tail for a possible undo to bring the tail back
946 view()->document()->insertText(afterPos, tailStr);
947 view()->setCursorPosition(currentPos);
948 view()->doc()->editEnd();
949
950 // Now remove the tail in a separate edit
951 KTextEditor::Cursor endPos = afterPos;
952 endPos.setColumn(afterPos.column() + tailStr.size());
953 view()->doc()->editStart();
954 view()->document()->removeText(KTextEditor::Range(afterPos, endPos));
955 }
956
957 view()->doc()->editEnd();
958 m_completionEditRunning = false;
959
960 abortCompletion();
961
962 view()->sendCompletionExecuted(start, model, toExecute);
963
964 KTextEditor::Cursor newPos = view()->cursorPosition();
965
966 if (newPos > *oldPos) {
967 m_automaticInvocationAt = newPos;
968 m_automaticInvocationLine = view()->doc()->text(KTextEditor::Range(*oldPos, newPos));
969 // qCDebug(LOG_KTE) << "executed, starting automatic invocation with line" << m_automaticInvocationLine;
970 m_lastInsertionByUser = false;
971 m_automaticInvocationTimer->start();
972 }
973
974 return true;
975}
976
977void KateCompletionWidget::resizeEvent(QResizeEvent *event)
978{
980
981 // keep argument hint geometry in sync
982 if (m_argumentHintWidget->isVisible()) {
983 updateArgumentHintGeometry();
984 }
985}
986
987void KateCompletionWidget::moveEvent(QMoveEvent *event)
988{
990
991 // keep argument hint geometry in sync
992 if (m_argumentHintWidget->isVisible()) {
993 updateArgumentHintGeometry();
994 }
995}
996
997void KateCompletionWidget::showEvent(QShowEvent *event)
998{
999 m_isSuspended = false;
1000 m_timeSinceShowing.start();
1001
1003
1004 if (!m_dontShowArgumentHints && m_argumentHintModel->rowCount(QModelIndex()) != 0) {
1005 m_argumentHintWidget->positionAndShow();
1006 }
1007}
1008
1009KTextEditor::MovingRange *KateCompletionWidget::completionRange(KTextEditor::CodeCompletionModel *model) const
1010{
1011 if (!model) {
1012 if (m_completionRanges.isEmpty()) {
1013 return nullptr;
1014 }
1015
1016 KTextEditor::MovingRange *ret = m_completionRanges.begin()->range;
1017
1018 for (const CompletionRange &range : m_completionRanges) {
1019 if (range.range->start() > ret->start()) {
1020 ret = range.range;
1021 }
1022 }
1023 return ret;
1024 }
1025 if (m_completionRanges.contains(model)) {
1026 return m_completionRanges[model].range;
1027 } else {
1028 return nullptr;
1029 }
1030}
1031
1033{
1034 return m_completionRanges;
1035}
1036
1037void KateCompletionWidget::modelReset()
1038{
1039 setUpdatesEnabled(false);
1040 m_entryList->setAnimated(false);
1041
1042 for (int row = 0; row < m_entryList->model()->rowCount(QModelIndex()); ++row) {
1043 QModelIndex index(m_entryList->model()->index(row, 0, QModelIndex()));
1044 if (!m_entryList->isExpanded(index)) {
1045 m_entryList->expand(index);
1046 }
1047 }
1048 setUpdatesEnabled(true);
1049}
1050
1051KateCompletionTree *KateCompletionWidget::treeView() const
1052{
1053 return m_entryList;
1054}
1055
1056QModelIndex KateCompletionWidget::selectedIndex() const
1057{
1058 if (!isCompletionActive()) {
1059 return QModelIndex();
1060 }
1061
1062 return m_entryList->currentIndex();
1063}
1064
1065bool KateCompletionWidget::navigateLeft()
1066{
1067 m_hadCompletionNavigation = true;
1068 if (currentEmbeddedWidget()) {
1069 QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetLeft");
1070 }
1071
1072 QModelIndex index = selectedIndex();
1073
1074 if (index.isValid()) {
1076
1077 return true;
1078 }
1079 return false;
1080}
1081
1083{
1084 m_hadCompletionNavigation = true;
1085 if (currentEmbeddedWidget()) { ///@todo post 4.2: Make these slots public interface, or create an interface using virtual functions
1086 QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetRight");
1087 }
1088
1089 QModelIndex index = selectedIndex();
1090
1091 if (index.isValid()) {
1093 return true;
1094 }
1095
1096 return false;
1097}
1098
1099bool KateCompletionWidget::navigateBack()
1100{
1101 m_hadCompletionNavigation = true;
1102 if (currentEmbeddedWidget()) {
1103 QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetBack");
1104 }
1105 return false;
1106}
1107
1108void KateCompletionWidget::toggleDocumentation()
1109{
1110 // user has configured the doc to be always visible
1111 // whenever its available.
1112 if (view()->config()->showDocWithCompletion()) {
1113 return;
1114 }
1115
1116 if (m_docTip->isVisible()) {
1117 m_hadCompletionNavigation = false;
1118 QTimer::singleShot(400, this, [this] {
1119 // if 400ms later this is not false, it means
1120 // that the user navigated inside the active
1121 // widget in doc tip
1122 if (!m_hadCompletionNavigation) {
1123 m_docTip->hide();
1124 }
1125 });
1126 } else {
1127 showDocTip(m_entryList->currentIndex());
1128 }
1129}
1130
1131void KateCompletionWidget::showDocTip(const QModelIndex &idx)
1132{
1134 // No data => hide
1135 if (!data.isValid()) {
1136 m_docTip->hide();
1137 return;
1138 } else if (data.canConvert<QWidget *>()) {
1139 m_docTip->setWidget(data.value<QWidget *>());
1140 } else if (data.canConvert<QString>()) {
1141 QString text = data.toString();
1142 if (text.isEmpty()) {
1143 m_docTip->hide();
1144 return;
1145 }
1146 m_docTip->setText(text);
1147 }
1148
1149 m_docTip->updatePosition(this);
1150 if (!m_docTip->isVisible()) {
1151 m_docTip->show();
1152 }
1153}
1154
1155bool KateCompletionWidget::handleShortcutOverride(QKeyEvent *e)
1156{
1157 if (!isCompletionActive() || e->modifiers() != Qt::AltModifier) {
1158 return false;
1159 }
1160 switch (e->key()) {
1161 case Qt::Key_Left:
1162 return navigateLeft();
1163 case Qt::Key_Right:
1164 return navigateRight();
1165 case Qt::Key_Up:
1166 return navigateUp();
1167 case Qt::Key_Down:
1168 return navigateDown();
1169 case Qt::Key_Return:
1170 return navigateAccept();
1171 case Qt::Key_Backspace:
1172 return navigateBack();
1173 }
1174 return false;
1175}
1176
1177bool KateCompletionWidget::eventFilter(QObject *watched, QEvent *event)
1178{
1179 if (watched != this && event->type() == QEvent::Resize && isCompletionActive()) {
1180 abortCompletion();
1181 }
1182 return QFrame::eventFilter(watched, event);
1183}
1184
1185bool KateCompletionWidget::navigateDown()
1186{
1187 m_hadCompletionNavigation = true;
1188 if (m_argumentHintModel->rowCount() > 0) {
1189 m_argumentHintWidget->selectNext();
1190 return true;
1191 } else if (currentEmbeddedWidget()) {
1192 QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetDown");
1193 }
1194 return false;
1195}
1196
1197bool KateCompletionWidget::navigateUp()
1198{
1199 m_hadCompletionNavigation = true;
1200 if (m_argumentHintModel->rowCount() > 0) {
1201 m_argumentHintWidget->selectPrevious();
1202 return true;
1203 } else if (currentEmbeddedWidget()) {
1204 QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetUp");
1205 }
1206 return false;
1207}
1208
1209QWidget *KateCompletionWidget::currentEmbeddedWidget()
1210{
1211 return m_docTip->currentWidget();
1212}
1213
1214void KateCompletionWidget::cursorDown()
1215{
1216 m_entryList->nextCompletion();
1217}
1218
1219void KateCompletionWidget::cursorUp()
1220{
1221 m_entryList->previousCompletion();
1222}
1223
1224void KateCompletionWidget::pageDown()
1225{
1226 m_entryList->pageDown();
1227}
1228
1229void KateCompletionWidget::pageUp()
1230{
1231 m_entryList->pageUp();
1232}
1233
1234void KateCompletionWidget::top()
1235{
1236 m_entryList->top();
1237}
1238
1239void KateCompletionWidget::bottom()
1240{
1241 m_entryList->bottom();
1242}
1243
1244void KateCompletionWidget::completionModelReset()
1245{
1247 if (!model) {
1248 qCWarning(LOG_KTE) << "bad sender";
1249 return;
1250 }
1251
1252 if (!m_waitingForReset.contains(model)) {
1253 return;
1254 }
1255
1256 m_waitingForReset.remove(model);
1257
1258 if (m_waitingForReset.isEmpty()) {
1259 if (!isCompletionActive()) {
1260 // qCDebug(LOG_KTE) << "all completion-models we waited for are ready. Last one: " << model->objectName();
1261 // Eventually show the completion-list if this was the last model we were waiting for
1262 // Use a queued connection once again to make sure that KateCompletionModel is notified before we are
1263 QMetaObject::invokeMethod(this, "modelContentChanged", Qt::QueuedConnection);
1264 }
1265 }
1266}
1267
1268void KateCompletionWidget::modelDestroyed(QObject *model)
1269{
1270 m_sourceModels.removeAll(model);
1271 abortCompletion();
1272}
1273
1274void KateCompletionWidget::registerCompletionModel(KTextEditor::CodeCompletionModel *model)
1275{
1276 if (m_sourceModels.contains(model)) {
1277 return;
1278 }
1279
1280 connect(model, &KTextEditor::CodeCompletionModel::destroyed, this, &KateCompletionWidget::modelDestroyed);
1281 // This connection must not be queued
1282 connect(model, &KTextEditor::CodeCompletionModel::modelReset, this, &KateCompletionWidget::completionModelReset);
1283
1284 m_sourceModels.append(model);
1285
1286 if (isCompletionActive()) {
1287 m_presentationModel->addCompletionModel(model);
1288 }
1289}
1290
1291void KateCompletionWidget::unregisterCompletionModel(KTextEditor::CodeCompletionModel *model)
1292{
1293 disconnect(model, &KTextEditor::CodeCompletionModel::destroyed, this, &KateCompletionWidget::modelDestroyed);
1294 disconnect(model, &KTextEditor::CodeCompletionModel::modelReset, this, &KateCompletionWidget::completionModelReset);
1295
1296 m_sourceModels.removeAll(model);
1297 abortCompletion();
1298}
1299
1300bool KateCompletionWidget::isCompletionModelRegistered(KTextEditor::CodeCompletionModel *model) const
1301{
1302 return m_sourceModels.contains(model);
1303}
1304
1305QList<KTextEditor::CodeCompletionModel *> KateCompletionWidget::codeCompletionModels() const
1306{
1307 return m_sourceModels;
1308}
1309
1310int KateCompletionWidget::automaticInvocationDelay() const
1311{
1312 return m_automaticInvocationDelay;
1313}
1314
1315void KateCompletionWidget::setIgnoreBufferSignals(bool ignore) const
1316{
1317 if (ignore) {
1318 disconnect(view()->doc(), &KTextEditor::Document::lineWrapped, this, &KateCompletionWidget::wrapLine);
1319 disconnect(view()->doc(), &KTextEditor::Document::lineUnwrapped, this, &KateCompletionWidget::unwrapLine);
1320 disconnect(view()->doc(), &KTextEditor::Document::textInserted, this, &KateCompletionWidget::insertText);
1321 disconnect(view()->doc(), &KTextEditor::Document::textRemoved, this, &KateCompletionWidget::removeText);
1322 } else {
1323 connect(view()->doc(), &KTextEditor::Document::lineWrapped, this, &KateCompletionWidget::wrapLine);
1324 connect(view()->doc(), &KTextEditor::Document::lineUnwrapped, this, &KateCompletionWidget::unwrapLine);
1325 connect(view()->doc(), &KTextEditor::Document::textInserted, this, &KateCompletionWidget::insertText);
1326 connect(view()->doc(), &KTextEditor::Document::textRemoved, this, &KateCompletionWidget::removeText);
1327 }
1328}
1329
1330void KateCompletionWidget::setAutomaticInvocationDelay(int delay)
1331{
1332 m_automaticInvocationDelay = delay;
1333}
1334
1335void KateCompletionWidget::wrapLine(KTextEditor::Document *, KTextEditor::Cursor)
1336{
1337 m_lastInsertionByUser = !m_completionEditRunning;
1338
1339 // wrap line, be done
1340 m_automaticInvocationLine.clear();
1341 m_automaticInvocationTimer->stop();
1342}
1343
1344void KateCompletionWidget::unwrapLine(KTextEditor::Document *, int)
1345{
1346 m_lastInsertionByUser = !m_completionEditRunning;
1347
1348 // just removal
1349 m_automaticInvocationLine.clear();
1350 m_automaticInvocationTimer->stop();
1351}
1352
1353void KateCompletionWidget::insertText(KTextEditor::Document *, KTextEditor::Cursor position, const QString &text)
1354{
1355 m_lastInsertionByUser = !m_completionEditRunning;
1356
1357 // no invoke?
1358 if (!view()->isAutomaticInvocationEnabled()) {
1359 m_automaticInvocationLine.clear();
1360 m_automaticInvocationTimer->stop();
1361 return;
1362 }
1363
1364 if (m_automaticInvocationAt != position) {
1365 m_automaticInvocationLine.clear();
1366 m_lastInsertionByUser = !m_completionEditRunning;
1367 }
1368
1369 m_automaticInvocationLine += text;
1370 m_automaticInvocationAt = position;
1371 m_automaticInvocationAt.setColumn(position.column() + text.length());
1372
1373 if (m_automaticInvocationLine.isEmpty()) {
1374 m_automaticInvocationTimer->stop();
1375 return;
1376 }
1377
1378 m_automaticInvocationTimer->start(m_automaticInvocationDelay);
1379}
1380
1381void KateCompletionWidget::removeText(KTextEditor::Document *, KTextEditor::Range, const QString &)
1382{
1383 m_lastInsertionByUser = !m_completionEditRunning;
1384
1385 // just removal
1386 m_automaticInvocationLine.clear();
1387 m_automaticInvocationTimer->stop();
1388}
1389
1390void KateCompletionWidget::automaticInvocation()
1391{
1392 // qCDebug(LOG_KTE)<<"m_automaticInvocationAt:"<<m_automaticInvocationAt;
1393 // qCDebug(LOG_KTE)<<view()->cursorPosition();
1394 if (m_automaticInvocationAt != view()->cursorPosition()) {
1395 return;
1396 }
1397
1398 bool start = false;
1400
1401 // qCDebug(LOG_KTE)<<"checking models";
1402 for (KTextEditor::CodeCompletionModel *model : std::as_const(m_sourceModels)) {
1403 // qCDebug(LOG_KTE)<<"m_completionRanges contains model?:"<<m_completionRanges.contains(model);
1404 if (m_completionRanges.contains(model)) {
1405 continue;
1406 }
1407
1408 start = _shouldStartCompletion(model, view(), m_automaticInvocationLine, m_lastInsertionByUser, view()->cursorPosition());
1409 // qCDebug(LOG_KTE)<<"start="<<start;
1410 if (start) {
1411 models << model;
1412 }
1413 }
1414 // qCDebug(LOG_KTE)<<"models found:"<<!models.isEmpty();
1415 if (!models.isEmpty()) {
1416 // Start automatic code completion
1417 startCompletion(KTextEditor::CodeCompletionModel::AutomaticInvocation, models);
1418 }
1419}
1420
1421void KateCompletionWidget::userInvokedCompletion()
1422{
1423 startCompletion(KTextEditor::CodeCompletionModel::UserInvocation);
1424}
1425
1426void KateCompletionWidget::tabCompletion(Direction direction)
1427{
1428 m_noAutoHide = true;
1429
1430 // Not using cursorDown/Up() as we don't want to go into the argument-hint list
1431 if (direction == Down) {
1432 const bool res = m_entryList->nextCompletion();
1433 if (!res) {
1434 m_entryList->top();
1435 }
1436 } else { // direction == Up
1437 const bool res = m_entryList->previousCompletion();
1438 if (!res) {
1439 m_entryList->bottom();
1440 }
1441 }
1442}
1443
1444void KateCompletionWidget::onDataChanged(const QModelIndex &topLeft, const QModelIndex &, const QList<int> &roles)
1445{
1446 // We only support updating documentation for 1 index at a time
1447 if ((roles.empty() || roles.contains(KTextEditor::CodeCompletionModel::ExpandingWidget)) && topLeft == m_entryList->currentIndex()) {
1448 showDocTip(topLeft);
1449 }
1450}
1451
1452#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()
qint64 elapsed() const const
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 & rx()
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 Sat Dec 21 2024 17:01:55 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.