File indexing completed on 2024-04-21 03:57:22

0001 /*
0002     SPDX-FileCopyrightText: 2005-2006 Hamish Rodda <rodda@kde.org>
0003     SPDX-FileCopyrightText: 2007-2008 David Nolden <david.nolden.kdevelop@art-master.de>
0004     SPDX-FileCopyrightText: 2022-2024 Waqar Ahmed <waqar.17a@gmail.com>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "katecompletionwidget.h"
0010 
0011 #include <ktexteditor/codecompletionmodelcontrollerinterface.h>
0012 
0013 #include "kateconfig.h"
0014 #include "katedocument.h"
0015 #include "kateglobal.h"
0016 #include "katerenderer.h"
0017 #include "kateview.h"
0018 
0019 #include "documentation_tip.h"
0020 #include "kateargumenthintmodel.h"
0021 #include "kateargumenthinttree.h"
0022 #include "katecompletionmodel.h"
0023 #include "katecompletiontree.h"
0024 #include "katepartdebug.h"
0025 
0026 #include <QAbstractScrollArea>
0027 #include <QApplication>
0028 #include <QBoxLayout>
0029 #include <QHeaderView>
0030 #include <QLabel>
0031 #include <QPushButton>
0032 #include <QScreen>
0033 #include <QScrollBar>
0034 #include <QSizeGrip>
0035 #include <QTimer>
0036 #include <QToolButton>
0037 
0038 const bool hideAutomaticCompletionOnExactMatch = true;
0039 
0040 #define CALLCI(WHAT, WHATELSE, WHAT2, model, FUNC)                                                                                                             \
0041     {                                                                                                                                                          \
0042         static KTextEditor::CodeCompletionModelControllerInterface defaultIf;                                                                                  \
0043         KTextEditor::CodeCompletionModelControllerInterface *ret = qobject_cast<KTextEditor::CodeCompletionModelControllerInterface *>(model);                 \
0044         if (!ret) {                                                                                                                                            \
0045             WHAT2 defaultIf.FUNC;                                                                                                                              \
0046         } else                                                                                                                                                 \
0047             WHAT2 ret->FUNC;                                                                                                                                   \
0048     }
0049 
0050 static KTextEditor::Range _completionRange(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view, KTextEditor::Cursor cursor)
0051 {
0052     CALLCI(return, , return, model, completionRange(view, cursor));
0053 }
0054 
0055 static KTextEditor::Range _updateRange(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view, KTextEditor::Range &range)
0056 {
0057     CALLCI(, return range, return, model, updateCompletionRange(view, range));
0058 }
0059 
0060 static QString _filterString(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view, const KTextEditor::Range &range, KTextEditor::Cursor cursor)
0061 {
0062     CALLCI(return, , return, model, filterString(view, range, cursor));
0063 }
0064 
0065 static bool
0066 _shouldAbortCompletion(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view, const KTextEditor::Range &range, const QString &currentCompletion)
0067 {
0068     CALLCI(return, , return, model, shouldAbortCompletion(view, range, currentCompletion));
0069 }
0070 
0071 static void _aborted(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view)
0072 {
0073     CALLCI(return, , return, model, aborted(view));
0074 }
0075 
0076 static bool _shouldStartCompletion(KTextEditor::CodeCompletionModel *model,
0077                                    KTextEditor::View *view,
0078                                    const QString &automaticInvocationLine,
0079                                    bool m_lastInsertionByUser,
0080                                    KTextEditor::Cursor cursor)
0081 {
0082     CALLCI(return, , return, model, shouldStartCompletion(view, automaticInvocationLine, m_lastInsertionByUser, cursor));
0083 }
0084 
0085 KateCompletionWidget::KateCompletionWidget(KTextEditor::ViewPrivate *parent)
0086     : QFrame(parent)
0087     , m_presentationModel(new KateCompletionModel(this))
0088     , m_view(parent)
0089     , m_entryList(new KateCompletionTree(this))
0090     , m_argumentHintModel(new KateArgumentHintModel(m_presentationModel))
0091     , m_argumentHintWidget(new ArgumentHintWidget(m_argumentHintModel, parent->renderer()->currentFont(), this, this))
0092     , m_docTip(new DocTip(this))
0093     , m_automaticInvocationDelay(100)
0094     , m_lastInsertionByUser(false)
0095     , m_isSuspended(false)
0096     , m_dontShowArgumentHints(false)
0097     , m_needShow(false)
0098     , m_hadCompletionNavigation(false)
0099     , m_noAutoHide(false)
0100     , m_completionEditRunning(false)
0101     , m_expandedAddedHeightBase(0)
0102     , m_lastInvocationType(KTextEditor::CodeCompletionModel::AutomaticInvocation)
0103 {
0104     if (parent->mainWindow() != KTextEditor::EditorPrivate::self()->dummyMainWindow() && parent->mainWindow()->window()) {
0105         setParent(parent->mainWindow()->window());
0106     } else if (auto w = m_view->window()) {
0107         setParent(w);
0108     } else if (auto w = QApplication::activeWindow()) {
0109         setParent(w);
0110     } else {
0111         setParent(parent);
0112     }
0113     m_docTip->setParent(this->parentWidget());
0114     parentWidget()->installEventFilter(this);
0115 
0116     setFrameStyle(QFrame::Box | QFrame::Raised);
0117     setLineWidth(1);
0118 
0119     m_entryList->setModel(m_presentationModel);
0120     m_entryList->setColumnWidth(0, 0); // These will be determined automatically in KateCompletionTree::resizeColumns
0121     m_entryList->setColumnWidth(1, 0);
0122     m_entryList->setColumnWidth(2, 0);
0123 
0124     m_argumentHintWidget->setParent(this->parentWidget());
0125 
0126     // trigger completion on double click on completion list
0127     connect(m_entryList, &KateCompletionTree::doubleClicked, this, &KateCompletionWidget::execute);
0128 
0129     connect(view(), &KTextEditor::ViewPrivate::focusOut, this, &KateCompletionWidget::viewFocusOut);
0130 
0131     m_automaticInvocationTimer = new QTimer(this);
0132     m_automaticInvocationTimer->setSingleShot(true);
0133     connect(m_automaticInvocationTimer, &QTimer::timeout, this, &KateCompletionWidget::automaticInvocation);
0134 
0135     // Keep branches expanded
0136     connect(m_presentationModel, &KateCompletionModel::modelReset, this, &KateCompletionWidget::modelReset);
0137     connect(m_presentationModel, &KateCompletionModel::rowsInserted, this, &KateCompletionWidget::rowsInserted);
0138     connect(m_argumentHintModel, &KateArgumentHintModel::contentStateChanged, this, &KateCompletionWidget::argumentHintsChanged);
0139 
0140     // No smart lock, no queued connects
0141     connect(view(), &KTextEditor::ViewPrivate::cursorPositionChanged, this, &KateCompletionWidget::cursorPositionChanged);
0142     connect(view(), &KTextEditor::ViewPrivate::verticalScrollPositionChanged, this, [this] {
0143         abortCompletion();
0144     });
0145 
0146     // connect to all possible editing primitives
0147     connect(view()->doc(), &KTextEditor::Document::lineWrapped, this, &KateCompletionWidget::wrapLine);
0148     connect(view()->doc(), &KTextEditor::Document::lineUnwrapped, this, &KateCompletionWidget::unwrapLine);
0149     connect(view()->doc(), &KTextEditor::Document::textInserted, this, &KateCompletionWidget::insertText);
0150     connect(view()->doc(), &KTextEditor::Document::textRemoved, this, &KateCompletionWidget::removeText);
0151 
0152     // This is a non-focus widget, it is passed keyboard input from the view
0153 
0154     // We need to do this, because else the focus goes to nirvana without any control when the completion-widget is clicked.
0155     setFocusPolicy(Qt::ClickFocus);
0156 
0157     const auto children = findChildren<QWidget *>();
0158     for (QWidget *childWidget : children) {
0159         childWidget->setFocusPolicy(Qt::NoFocus);
0160     }
0161 
0162     // Position the entry-list so a frame can be drawn around it
0163     m_entryList->move(frameWidth(), frameWidth());
0164 
0165     hide();
0166     m_docTip->setVisible(false);
0167 }
0168 
0169 KateCompletionWidget::~KateCompletionWidget()
0170 {
0171     // ensure no slot triggered during destruction => else we access already invalidated stuff
0172     m_presentationModel->disconnect(this);
0173     m_argumentHintModel->disconnect(this);
0174 
0175     delete m_docTip;
0176 }
0177 
0178 void KateCompletionWidget::viewFocusOut()
0179 {
0180     QWidget *toplevels[3] = {m_entryList, m_docTip, m_argumentHintWidget};
0181     if (!std::any_of(std::begin(toplevels), std::end(toplevels), [](QWidget *w) {
0182             auto fw = QApplication::focusWidget();
0183             return fw == w || w->isAncestorOf(fw);
0184         })) {
0185         abortCompletion();
0186     }
0187 }
0188 
0189 void KateCompletionWidget::focusOutEvent(QFocusEvent *)
0190 {
0191     abortCompletion();
0192 }
0193 
0194 void KateCompletionWidget::modelContentChanged()
0195 {
0196     ////qCDebug(LOG_KTE)<<">>>>>>>>>>>>>>>>";
0197     if (m_completionRanges.isEmpty()) {
0198         // qCDebug(LOG_KTE) << "content changed, but no completion active";
0199         abortCompletion();
0200         return;
0201     }
0202 
0203     if (!view()->hasFocus()) {
0204         // qCDebug(LOG_KTE) << "view does not have focus";
0205         return;
0206     }
0207 
0208     if (!m_waitingForReset.isEmpty()) {
0209         // qCDebug(LOG_KTE) << "waiting for" << m_waitingForReset.size() << "completion-models to reset";
0210         return;
0211     }
0212 
0213     int realItemCount = 0;
0214     const auto completionModels = m_presentationModel->completionModels();
0215     for (KTextEditor::CodeCompletionModel *model : completionModels) {
0216         realItemCount += model->rowCount();
0217     }
0218     if (!m_isSuspended && ((isHidden() && m_argumentHintWidget->isHidden()) || m_needShow) && realItemCount != 0) {
0219         m_needShow = false;
0220         updateAndShow();
0221     }
0222 
0223     if (m_argumentHintModel->rowCount(QModelIndex()) == 0) {
0224         m_argumentHintWidget->hide();
0225     }
0226 
0227     if (m_presentationModel->rowCount(QModelIndex()) == 0) {
0228         hide();
0229     }
0230 
0231     // For automatic invocations, only autoselect first completion entry when enabled in the config
0232     if (m_lastInvocationType != KTextEditor::CodeCompletionModel::AutomaticInvocation || view()->config()->automaticCompletionPreselectFirst()) {
0233         m_entryList->setCurrentIndex(model()->index(0, 0));
0234     }
0235     // 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
0236     if (!model()->indexIsItem(m_entryList->currentIndex())) {
0237         QModelIndex firstIndex = model()->index(0, 0, m_entryList->currentIndex());
0238         m_entryList->setCurrentIndex(firstIndex);
0239         // m_entryList->scrollTo(firstIndex, QAbstractItemView::PositionAtTop);
0240     }
0241 
0242     updateHeight();
0243 
0244     // New items for the argument-hint tree may have arrived, so check whether it needs to be shown
0245     if (m_argumentHintWidget->isHidden() && !m_dontShowArgumentHints && m_argumentHintModel->rowCount(QModelIndex()) != 0) {
0246         m_argumentHintWidget->positionAndShow();
0247     }
0248 
0249     if (!m_noAutoHide && hideAutomaticCompletionOnExactMatch && !isHidden() && m_lastInvocationType == KTextEditor::CodeCompletionModel::AutomaticInvocation
0250         && m_presentationModel->shouldMatchHideCompletionList()) {
0251         hide();
0252     } else if (isHidden() && !m_presentationModel->shouldMatchHideCompletionList() && m_presentationModel->rowCount(QModelIndex())) {
0253         show();
0254     }
0255 }
0256 
0257 KateArgumentHintModel *KateCompletionWidget::argumentHintModel() const
0258 {
0259     return m_argumentHintModel;
0260 }
0261 
0262 const KateCompletionModel *KateCompletionWidget::model() const
0263 {
0264     return m_presentationModel;
0265 }
0266 
0267 KateCompletionModel *KateCompletionWidget::model()
0268 {
0269     return m_presentationModel;
0270 }
0271 
0272 void KateCompletionWidget::rowsInserted(const QModelIndex &parent, int rowFrom, int rowEnd)
0273 {
0274     m_entryList->setAnimated(false);
0275 
0276     if (!parent.isValid()) {
0277         for (int i = rowFrom; i <= rowEnd; ++i) {
0278             m_entryList->expand(m_presentationModel->index(i, 0, parent));
0279         }
0280     }
0281 }
0282 
0283 KTextEditor::ViewPrivate *KateCompletionWidget::view() const
0284 {
0285     return m_view;
0286 }
0287 
0288 void KateCompletionWidget::argumentHintsChanged(bool hasContent)
0289 {
0290     m_dontShowArgumentHints = !hasContent;
0291 
0292     if (m_dontShowArgumentHints) {
0293         m_argumentHintWidget->hide();
0294     } else {
0295         updateArgumentHintGeometry();
0296     }
0297 }
0298 
0299 void KateCompletionWidget::startCompletion(KTextEditor::CodeCompletionModel::InvocationType invocationType,
0300                                            const QList<KTextEditor::CodeCompletionModel *> &models)
0301 {
0302     if (invocationType == KTextEditor::CodeCompletionModel::UserInvocation) {
0303         abortCompletion();
0304     }
0305     startCompletion(KTextEditor::Range(KTextEditor::Cursor(-1, -1), KTextEditor::Cursor(-1, -1)), models, invocationType);
0306 }
0307 
0308 void KateCompletionWidget::deleteCompletionRanges()
0309 {
0310     for (const CompletionRange &r : std::as_const(m_completionRanges)) {
0311         delete r.range;
0312     }
0313     m_completionRanges.clear();
0314 }
0315 
0316 void KateCompletionWidget::startCompletion(KTextEditor::Range word,
0317                                            KTextEditor::CodeCompletionModel *model,
0318                                            KTextEditor::CodeCompletionModel::InvocationType invocationType)
0319 {
0320     QList<KTextEditor::CodeCompletionModel *> models;
0321     if (model) {
0322         models << model;
0323     } else {
0324         models = m_sourceModels;
0325     }
0326     startCompletion(word, models, invocationType);
0327 }
0328 
0329 void KateCompletionWidget::startCompletion(KTextEditor::Range word,
0330                                            const QList<KTextEditor::CodeCompletionModel *> &modelsToStart,
0331                                            KTextEditor::CodeCompletionModel::InvocationType invocationType)
0332 {
0333     ////qCDebug(LOG_KTE)<<"============";
0334 
0335     m_isSuspended = false;
0336     m_needShow = true;
0337 
0338     if (m_completionRanges.isEmpty()) {
0339         m_noAutoHide = false; // Re-enable auto-hide on every clean restart of the completion
0340     }
0341 
0342     m_lastInvocationType = invocationType;
0343 
0344     disconnect(this->model(), &KateCompletionModel::layoutChanged, this, &KateCompletionWidget::modelContentChanged);
0345     disconnect(this->model(), &KateCompletionModel::modelReset, this, &KateCompletionWidget::modelContentChanged);
0346 
0347     m_dontShowArgumentHints = true;
0348 
0349     QList<KTextEditor::CodeCompletionModel *> models = (modelsToStart.isEmpty() ? m_sourceModels : modelsToStart);
0350 
0351     for (auto it = m_completionRanges.keyBegin(), end = m_completionRanges.keyEnd(); it != end; ++it) {
0352         KTextEditor::CodeCompletionModel *model = *it;
0353         if (!models.contains(model)) {
0354             models << model;
0355         }
0356     }
0357 
0358     m_presentationModel->clearCompletionModels();
0359 
0360     if (invocationType == KTextEditor::CodeCompletionModel::UserInvocation) {
0361         deleteCompletionRanges();
0362     }
0363 
0364     for (KTextEditor::CodeCompletionModel *model : std::as_const(models)) {
0365         KTextEditor::Range range;
0366         if (word.isValid()) {
0367             range = word;
0368             // qCDebug(LOG_KTE)<<"word is used";
0369         } else {
0370             range = _completionRange(model, view(), view()->cursorPosition());
0371             // qCDebug(LOG_KTE)<<"completionRange has been called, cursor pos is"<<view()->cursorPosition();
0372         }
0373         // qCDebug(LOG_KTE)<<"range is"<<range;
0374         if (!range.isValid()) {
0375             if (m_completionRanges.contains(model)) {
0376                 KTextEditor::MovingRange *oldRange = m_completionRanges[model].range;
0377                 // qCDebug(LOG_KTE)<<"removing completion range 1";
0378                 m_completionRanges.remove(model);
0379                 delete oldRange;
0380             }
0381             models.removeAll(model);
0382             continue;
0383         }
0384         if (m_completionRanges.contains(model)) {
0385             if (*m_completionRanges[model].range == range) {
0386                 continue; // Leave it running as it is
0387             } else { // delete the range that was used previously
0388                 KTextEditor::MovingRange *oldRange = m_completionRanges[model].range;
0389                 // qCDebug(LOG_KTE)<<"removing completion range 2";
0390                 m_completionRanges.remove(model);
0391                 delete oldRange;
0392             }
0393         }
0394 
0395         connect(model, &KTextEditor::CodeCompletionModel::waitForReset, this, &KateCompletionWidget::waitForModelReset);
0396 
0397         // qCDebug(LOG_KTE)<<"Before completion invoke: range:"<<range;
0398         model->completionInvoked(view(), range, invocationType);
0399 
0400         disconnect(model, &KTextEditor::CodeCompletionModel::waitForReset, this, &KateCompletionWidget::waitForModelReset);
0401 
0402         m_completionRanges[model] =
0403             CompletionRange(view()->doc()->newMovingRange(range, KTextEditor::MovingRange::ExpandRight | KTextEditor::MovingRange::ExpandLeft));
0404 
0405         // In automatic invocation mode, hide the completion widget as soon as the position where the completion was started is passed to the left
0406         m_completionRanges[model].leftBoundary = view()->cursorPosition();
0407 
0408         // In manual invocation mode, bound the activity either the point from where completion was invoked, or to the start of the range
0409         if (invocationType != KTextEditor::CodeCompletionModel::AutomaticInvocation) {
0410             if (range.start() < m_completionRanges[model].leftBoundary) {
0411                 m_completionRanges[model].leftBoundary = range.start();
0412             }
0413         }
0414 
0415         if (!m_completionRanges[model].range->toRange().isValid()) {
0416             qCWarning(LOG_KTE) << "Could not construct valid smart-range from" << range << "instead got" << *m_completionRanges[model].range;
0417             abortCompletion();
0418             return;
0419         }
0420     }
0421 
0422     m_presentationModel->setCompletionModels(models);
0423 
0424     cursorPositionChanged();
0425 
0426     if (!m_completionRanges.isEmpty()) {
0427         connect(this->model(), &KateCompletionModel::layoutChanged, this, &KateCompletionWidget::modelContentChanged);
0428         connect(this->model(), &KateCompletionModel::modelReset, this, &KateCompletionWidget::modelContentChanged);
0429         // Now that all models have been notified, check whether the widget should be displayed instantly
0430         modelContentChanged();
0431     } else {
0432         abortCompletion();
0433     }
0434 }
0435 
0436 QString KateCompletionWidget::tailString() const
0437 {
0438     if (!KateViewConfig::global()->wordCompletionRemoveTail()) {
0439         return QString();
0440     }
0441 
0442     const int line = view()->cursorPosition().line();
0443     const int column = view()->cursorPosition().column();
0444 
0445     const QString text = view()->document()->line(line);
0446 
0447     static constexpr auto options = QRegularExpression::UseUnicodePropertiesOption | QRegularExpression::DontCaptureOption;
0448     static const QRegularExpression findWordEnd(QStringLiteral("^[_\\w]*\\b"), options);
0449 
0450     QRegularExpressionMatch match = findWordEnd.match(text.mid(column));
0451     if (match.hasMatch()) {
0452         return match.captured(0);
0453     }
0454     return QString();
0455 }
0456 
0457 void KateCompletionWidget::waitForModelReset()
0458 {
0459     KTextEditor::CodeCompletionModel *senderModel = qobject_cast<KTextEditor::CodeCompletionModel *>(sender());
0460     if (!senderModel) {
0461         qCWarning(LOG_KTE) << "waitForReset signal from bad model";
0462         return;
0463     }
0464     m_waitingForReset.insert(senderModel);
0465 }
0466 
0467 void KateCompletionWidget::updateAndShow()
0468 {
0469     // qCDebug(LOG_KTE)<<"*******************************************";
0470     if (!view()->hasFocus()) {
0471         qCDebug(LOG_KTE) << "view does not have focus";
0472         return;
0473     }
0474 
0475     setUpdatesEnabled(false);
0476 
0477     modelReset();
0478 
0479     m_argumentHintModel->buildRows();
0480     if (m_argumentHintModel->rowCount(QModelIndex()) != 0) {
0481         argumentHintsChanged(true);
0482     }
0483 
0484     // update height first
0485     updateHeight();
0486     // then resize columns afterwards because we need height information
0487     m_entryList->resizeColumns(true, true);
0488     // lastly update position as now we have height and width
0489     updatePosition(true);
0490 
0491     setUpdatesEnabled(true);
0492 
0493     if (m_argumentHintModel->rowCount(QModelIndex())) {
0494         updateArgumentHintGeometry();
0495         m_argumentHintWidget->positionAndShow();
0496     } else {
0497         m_argumentHintWidget->hide();
0498     }
0499 
0500     if (m_presentationModel->rowCount()
0501         && (!m_presentationModel->shouldMatchHideCompletionList() || !hideAutomaticCompletionOnExactMatch
0502             || m_lastInvocationType != KTextEditor::CodeCompletionModel::AutomaticInvocation)) {
0503         show();
0504     } else {
0505         hide();
0506     }
0507 }
0508 
0509 void KateCompletionWidget::updatePosition(bool force)
0510 {
0511     if (!force && !isCompletionActive()) {
0512         return;
0513     }
0514 
0515     if (!completionRange()) {
0516         return;
0517     }
0518     const QPoint localCursorCoord = view()->cursorToCoordinate(completionRange()->start());
0519     if (localCursorCoord == QPoint(-1, -1)) {
0520         // Start of completion range is now off-screen -> abort
0521         abortCompletion();
0522         return;
0523     }
0524 
0525     const QPoint cursorCoordinate = view()->mapToGlobal(localCursorCoord);
0526     QPoint p = cursorCoordinate;
0527     int x = p.x();
0528     int y = p.y();
0529 
0530     y += view()->renderer()->currentFontMetrics().height() + 2;
0531 
0532     const auto windowGeometry = parentWidget()->geometry();
0533     if (x + width() > windowGeometry.right()) {
0534         // crossing right edge
0535         x = windowGeometry.right() - width();
0536     }
0537     if (x < windowGeometry.left()) {
0538         x = windowGeometry.left();
0539     }
0540 
0541     if (y + height() > windowGeometry.bottom()) {
0542         // move above cursor if we are crossing the bottom
0543         y -= height();
0544         if (y + height() > cursorCoordinate.y()) {
0545             y -= (y + height()) - cursorCoordinate.y();
0546             y -= 2;
0547         }
0548     }
0549 
0550     move(parentWidget()->mapFromGlobal(QPoint(x, y)));
0551 }
0552 
0553 void KateCompletionWidget::updateArgumentHintGeometry()
0554 {
0555     if (!m_dontShowArgumentHints) {
0556         // Now place the argument-hint widget
0557         m_argumentHintWidget->updateGeometry();
0558     }
0559 }
0560 
0561 // Checks whether the given model has at least "rows" rows, also searching the second level of the tree.
0562 bool hasAtLeastNRows(int rows, QAbstractItemModel *model)
0563 {
0564     int count = 0;
0565     for (int row = 0; row < model->rowCount(); ++row) {
0566         ++count;
0567 
0568         QModelIndex index(model->index(row, 0));
0569         if (index.isValid()) {
0570             count += model->rowCount(index);
0571         }
0572 
0573         if (count > rows) {
0574             return true;
0575         }
0576     }
0577     return false;
0578 }
0579 
0580 void KateCompletionWidget::updateHeight()
0581 {
0582     QRect geom = geometry();
0583 
0584     constexpr int minBaseHeight = 10;
0585     constexpr int maxBaseHeight = 300;
0586 
0587     int baseHeight = 0;
0588     int calculatedCustomHeight = 0;
0589 
0590     if (hasAtLeastNRows(15, m_presentationModel)) {
0591         // If we know there is enough rows, always use max-height, we don't need to calculate size-hints
0592         baseHeight = maxBaseHeight;
0593     } else {
0594         // Calculate size-hints to determine the best height
0595         for (int row = 0; row < m_presentationModel->rowCount(); ++row) {
0596             baseHeight += treeView()->sizeHintForRow(row);
0597 
0598             QModelIndex index(m_presentationModel->index(row, 0));
0599             if (index.isValid()) {
0600                 for (int row2 = 0; row2 < m_presentationModel->rowCount(index); ++row2) {
0601                     int h = 0;
0602                     for (int a = 0; a < m_presentationModel->columnCount(index); ++a) {
0603                         const QModelIndex child = m_presentationModel->index(row2, a, index);
0604                         int localHeight = treeView()->sizeHintForIndex(child).height();
0605                         if (localHeight > h) {
0606                             h = localHeight;
0607                         }
0608                     }
0609                     baseHeight += h;
0610                     if (baseHeight > maxBaseHeight) {
0611                         break;
0612                     }
0613                 }
0614 
0615                 if (baseHeight > maxBaseHeight) {
0616                     break;
0617                 }
0618             }
0619         }
0620 
0621         calculatedCustomHeight = baseHeight;
0622     }
0623 
0624     baseHeight += 2 * frameWidth();
0625 
0626     if (m_entryList->horizontalScrollBar()->isVisible()) {
0627         baseHeight += m_entryList->horizontalScrollBar()->height();
0628     }
0629 
0630     if (baseHeight < minBaseHeight) {
0631         baseHeight = minBaseHeight;
0632     }
0633     if (baseHeight > maxBaseHeight) {
0634         baseHeight = maxBaseHeight;
0635         m_entryList->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
0636     } else {
0637         // Somewhere there seems to be a bug that makes QTreeView add a scroll-bar
0638         // even if the content exactly fits in. So forcefully disable the scroll-bar in that case
0639         m_entryList->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0640     }
0641 
0642     int newExpandingAddedHeight = 0;
0643 
0644     if (baseHeight == maxBaseHeight) {
0645         // Eventually add some more height
0646         if (calculatedCustomHeight && calculatedCustomHeight > baseHeight && calculatedCustomHeight < maxBaseHeight) {
0647             newExpandingAddedHeight = calculatedCustomHeight - baseHeight;
0648         }
0649     }
0650 
0651     if (m_expandedAddedHeightBase != baseHeight && m_expandedAddedHeightBase - baseHeight > -2 && m_expandedAddedHeightBase - baseHeight < 2) {
0652         // Re-use the stored base-height if it only slightly differs from the current one.
0653         // Reason: Qt seems to apply slightly wrong sizes when the completion-widget is moved out of the screen at the bottom,
0654         //        which completely breaks this algorithm. Solution: re-use the old base-size if it only slightly differs from the computed one.
0655         baseHeight = m_expandedAddedHeightBase;
0656     }
0657 
0658     int finalHeight = baseHeight + newExpandingAddedHeight;
0659 
0660     if (finalHeight < 10) {
0661         m_entryList->resize(m_entryList->width(), height() - 2 * frameWidth());
0662         return;
0663     }
0664 
0665     m_expandedAddedHeightBase = geometry().height();
0666 
0667     geom.setHeight(finalHeight);
0668 
0669     // Work around a crash deep within the Qt 4.5 raster engine
0670     m_entryList->setScrollingEnabled(false);
0671 
0672     if (geometry() != geom) {
0673         setGeometry(geom);
0674     }
0675 
0676     QSize entryListSize = QSize(m_entryList->width(), finalHeight - 2 * frameWidth());
0677     if (m_entryList->size() != entryListSize) {
0678         m_entryList->resize(entryListSize);
0679     }
0680 
0681     m_entryList->setScrollingEnabled(true);
0682 }
0683 
0684 void KateCompletionWidget::cursorPositionChanged()
0685 {
0686     ////qCDebug(LOG_KTE);
0687     if (m_completionRanges.isEmpty()) {
0688         return;
0689     }
0690 
0691     QModelIndex oldCurrentSourceIndex;
0692     if (m_entryList->currentIndex().isValid()) {
0693         oldCurrentSourceIndex = m_presentationModel->mapToSource(m_entryList->currentIndex());
0694     }
0695 
0696     QMap<KTextEditor::CodeCompletionModel *, QString> filterStringByModel;
0697 
0698     disconnect(this->model(), &KateCompletionModel::layoutChanged, this, &KateCompletionWidget::modelContentChanged);
0699     disconnect(this->model(), &KateCompletionModel::modelReset, this, &KateCompletionWidget::modelContentChanged);
0700 
0701     // Check the models and eventually abort some
0702     const QList<KTextEditor::CodeCompletionModel *> checkCompletionRanges = m_completionRanges.keys();
0703     for (auto model : checkCompletionRanges) {
0704         if (!m_completionRanges.contains(model)) {
0705             continue;
0706         }
0707 
0708         // qCDebug(LOG_KTE)<<"range before _updateRange:"<< *range;
0709 
0710         // this might invalidate the range, therefore re-check afterwards
0711         KTextEditor::Range rangeTE = m_completionRanges[model].range->toRange();
0712         KTextEditor::Range newRange = _updateRange(model, view(), rangeTE);
0713         if (!m_completionRanges.contains(model)) {
0714             continue;
0715         }
0716 
0717         // update value
0718         m_completionRanges[model].range->setRange(newRange);
0719 
0720         // qCDebug(LOG_KTE)<<"range after _updateRange:"<< *range;
0721         QString currentCompletion = _filterString(model, view(), *m_completionRanges[model].range, view()->cursorPosition());
0722         if (!m_completionRanges.contains(model)) {
0723             continue;
0724         }
0725 
0726         // qCDebug(LOG_KTE)<<"after _filterString, currentCompletion="<< currentCompletion;
0727         bool abort = _shouldAbortCompletion(model, view(), *m_completionRanges[model].range, currentCompletion);
0728         if (!m_completionRanges.contains(model)) {
0729             continue;
0730         }
0731 
0732         // qCDebug(LOG_KTE)<<"after _shouldAbortCompletion:abort="<<abort;
0733         if (view()->cursorPosition() < m_completionRanges[model].leftBoundary) {
0734             // qCDebug(LOG_KTE) << "aborting because of boundary:
0735             // cursor:"<<view()->cursorPosition()<<"completion_Range_left_boundary:"<<m_completionRanges[*it].leftBoundary;
0736             abort = true;
0737         }
0738 
0739         if (!m_completionRanges.contains(model)) {
0740             continue;
0741         }
0742 
0743         if (abort) {
0744             if (m_completionRanges.count() == 1) {
0745                 // last model - abort whole completion
0746                 abortCompletion();
0747                 return;
0748             } else {
0749                 {
0750                     delete m_completionRanges[model].range;
0751                     // qCDebug(LOG_KTE)<<"removing completion range 3";
0752                     m_completionRanges.remove(model);
0753                 }
0754 
0755                 _aborted(model, view());
0756                 m_presentationModel->removeCompletionModel(model);
0757             }
0758         } else {
0759             filterStringByModel[model] = currentCompletion;
0760         }
0761     }
0762 
0763     connect(this->model(), &KateCompletionModel::layoutChanged, this, &KateCompletionWidget::modelContentChanged);
0764     connect(this->model(), &KateCompletionModel::modelReset, this, &KateCompletionWidget::modelContentChanged);
0765 
0766     m_presentationModel->setCurrentCompletion(filterStringByModel);
0767 
0768     if (oldCurrentSourceIndex.isValid()) {
0769         QModelIndex idx = m_presentationModel->mapFromSource(oldCurrentSourceIndex);
0770         // We only want to reselect this if it is still the first item
0771         if (idx.isValid() && idx.row() == 0) {
0772             // qCDebug(LOG_KTE) << "setting" << idx;
0773             m_entryList->setCurrentIndex(idx.sibling(idx.row(), 0));
0774             //       m_entryList->nextCompletion();
0775             //       m_entryList->previousCompletion();
0776         } else {
0777             // qCDebug(LOG_KTE) << "failed to map from source";
0778         }
0779     }
0780 
0781     m_entryList->scheduleUpdate();
0782 }
0783 
0784 bool KateCompletionWidget::isCompletionActive() const
0785 {
0786     return !m_completionRanges.isEmpty() && ((!isHidden() && isVisible()) || (!m_argumentHintWidget->isHidden() && m_argumentHintWidget->isVisible()));
0787 }
0788 
0789 void KateCompletionWidget::abortCompletion()
0790 {
0791     // qCDebug(LOG_KTE) ;
0792 
0793     m_isSuspended = false;
0794 
0795     if (!docTip()->isHidden()) {
0796         docTip()->hide();
0797     }
0798 
0799     bool wasActive = isCompletionActive();
0800 
0801     clear();
0802 
0803     if (!isHidden()) {
0804         hide();
0805     }
0806 
0807     if (!m_argumentHintWidget->isHidden()) {
0808         m_argumentHintWidget->hide();
0809     }
0810 
0811     if (wasActive) {
0812         view()->sendCompletionAborted();
0813     }
0814 }
0815 
0816 void KateCompletionWidget::clear()
0817 {
0818     m_presentationModel->clearCompletionModels();
0819     m_argumentHintModel->clear();
0820     m_docTip->clearWidgets();
0821 
0822     const auto keys = m_completionRanges.keys();
0823     for (KTextEditor::CodeCompletionModel *model : keys) {
0824         _aborted(model, view());
0825     }
0826 
0827     deleteCompletionRanges();
0828 }
0829 
0830 bool KateCompletionWidget::navigateAccept()
0831 {
0832     m_hadCompletionNavigation = true;
0833 
0834     if (currentEmbeddedWidget()) {
0835         QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetAccept");
0836     }
0837 
0838     QModelIndex index = selectedIndex();
0839     if (index.isValid()) {
0840         index.data(KTextEditor::CodeCompletionModel::AccessibilityAccept);
0841         return true;
0842     }
0843     return false;
0844 }
0845 
0846 bool KateCompletionWidget::execute()
0847 {
0848     // qCDebug(LOG_KTE) ;
0849 
0850     if (!isCompletionActive()) {
0851         return false;
0852     }
0853 
0854     QModelIndex index = selectedIndex();
0855 
0856     if (!index.isValid()) {
0857         abortCompletion();
0858         return false;
0859     }
0860 
0861     QModelIndex toExecute;
0862 
0863     if (index.model() == m_presentationModel) {
0864         toExecute = m_presentationModel->mapToSource(index);
0865     } else {
0866         toExecute = m_argumentHintModel->mapToSource(index);
0867     }
0868 
0869     if (!toExecute.isValid()) {
0870         qCWarning(LOG_KTE) << "Could not map index" << m_entryList->selectionModel()->currentIndex() << "to source index.";
0871         abortCompletion();
0872         return false;
0873     }
0874 
0875     // encapsulate all editing as being from the code completion, and undo-able in one step.
0876     view()->doc()->editStart();
0877     m_completionEditRunning = true;
0878 
0879     // create scoped pointer, to ensure deletion of cursor
0880     std::unique_ptr<KTextEditor::MovingCursor> oldPos(view()->doc()->newMovingCursor(view()->cursorPosition(), KTextEditor::MovingCursor::StayOnInsert));
0881 
0882     KTextEditor::CodeCompletionModel *model = static_cast<KTextEditor::CodeCompletionModel *>(const_cast<QAbstractItemModel *>(toExecute.model()));
0883     Q_ASSERT(model);
0884 
0885     Q_ASSERT(m_completionRanges.contains(model));
0886 
0887     KTextEditor::Cursor start = m_completionRanges[model].range->start();
0888 
0889     // Save the "tail"
0890     QString tailStr = tailString();
0891     std::unique_ptr<KTextEditor::MovingCursor> afterTailMCursor(view()->doc()->newMovingCursor(view()->cursorPosition()));
0892     afterTailMCursor->move(tailStr.size());
0893 
0894     // Handle completion for multi cursors
0895     std::shared_ptr<QMetaObject::Connection> connection(new QMetaObject::Connection());
0896     auto autoCompleteMulticursors = [connection, this](KTextEditor::Document *document, const KTextEditor::Range &range) {
0897         disconnect(*connection);
0898         const QString text = document->text(range);
0899         if (text.isEmpty()) {
0900             return;
0901         }
0902         const auto &multicursors = view()->secondaryCursors();
0903         for (const auto &c : multicursors) {
0904             const KTextEditor::Cursor pos = c.cursor();
0905             KTextEditor::Range wordToReplace = view()->doc()->wordRangeAt(pos);
0906             wordToReplace.setEnd(pos); // limit the word to the current cursor position
0907             view()->doc()->replaceText(wordToReplace, text);
0908         }
0909     };
0910     *connection = connect(view()->doc(), &KTextEditor::DocumentPrivate::textInsertedRange, this, autoCompleteMulticursors);
0911 
0912     model->executeCompletionItem(view(), *m_completionRanges[model].range, toExecute);
0913     // NOTE the CompletionRange is now removed from m_completionRanges
0914 
0915     // There are situations where keeping the tail is beneficial, but with the "Remove tail on complete" option is enabled,
0916     // the tail is removed. For these situations we convert the completion into two edits:
0917     // 1) Insert the completion
0918     // 2) Remove the tail
0919     //
0920     // When we encounter one of these situations we can just do _one_ undo to have the tail back.
0921     //
0922     // Technically the tail is already removed by "executeCompletionItem()", so before this call we save the possible tail
0923     // and re-add the tail before we end the first grouped "edit". Then immediately after that we add a second edit that
0924     // removes the tail again.
0925     // NOTE: The ViInputMode makes assumptions about the edit actions in a completion and breaks if we insert extra
0926     // edits here, so we just disable this feature for ViInputMode
0927     if (!tailStr.isEmpty() && view()->viewInputMode() != KTextEditor::View::ViInputMode) {
0928         KTextEditor::Cursor currentPos = view()->cursorPosition();
0929         KTextEditor::Cursor afterPos = afterTailMCursor->toCursor();
0930         // Re add the tail for a possible undo to bring the tail back
0931         view()->document()->insertText(afterPos, tailStr);
0932         view()->setCursorPosition(currentPos);
0933         view()->doc()->editEnd();
0934 
0935         // Now remove the tail in a separate edit
0936         KTextEditor::Cursor endPos = afterPos;
0937         endPos.setColumn(afterPos.column() + tailStr.size());
0938         view()->doc()->editStart();
0939         view()->document()->removeText(KTextEditor::Range(afterPos, endPos));
0940     }
0941 
0942     view()->doc()->editEnd();
0943     m_completionEditRunning = false;
0944 
0945     abortCompletion();
0946 
0947     view()->sendCompletionExecuted(start, model, toExecute);
0948 
0949     KTextEditor::Cursor newPos = view()->cursorPosition();
0950 
0951     if (newPos > *oldPos) {
0952         m_automaticInvocationAt = newPos;
0953         m_automaticInvocationLine = view()->doc()->text(KTextEditor::Range(*oldPos, newPos));
0954         // qCDebug(LOG_KTE) << "executed, starting automatic invocation with line" << m_automaticInvocationLine;
0955         m_lastInsertionByUser = false;
0956         m_automaticInvocationTimer->start();
0957     }
0958 
0959     return true;
0960 }
0961 
0962 void KateCompletionWidget::resizeEvent(QResizeEvent *event)
0963 {
0964     QFrame::resizeEvent(event);
0965 
0966     // keep argument hint geometry in sync
0967     if (m_argumentHintWidget->isVisible()) {
0968         updateArgumentHintGeometry();
0969     }
0970 }
0971 
0972 void KateCompletionWidget::moveEvent(QMoveEvent *event)
0973 {
0974     QFrame::moveEvent(event);
0975 
0976     // keep argument hint geometry in sync
0977     if (m_argumentHintWidget->isVisible()) {
0978         updateArgumentHintGeometry();
0979     }
0980 }
0981 
0982 void KateCompletionWidget::showEvent(QShowEvent *event)
0983 {
0984     m_isSuspended = false;
0985 
0986     QFrame::showEvent(event);
0987 
0988     if (!m_dontShowArgumentHints && m_argumentHintModel->rowCount(QModelIndex()) != 0) {
0989         m_argumentHintWidget->positionAndShow();
0990     }
0991 }
0992 
0993 KTextEditor::MovingRange *KateCompletionWidget::completionRange(KTextEditor::CodeCompletionModel *model) const
0994 {
0995     if (!model) {
0996         if (m_completionRanges.isEmpty()) {
0997             return nullptr;
0998         }
0999 
1000         KTextEditor::MovingRange *ret = m_completionRanges.begin()->range;
1001 
1002         for (const CompletionRange &range : m_completionRanges) {
1003             if (range.range->start() > ret->start()) {
1004                 ret = range.range;
1005             }
1006         }
1007         return ret;
1008     }
1009     if (m_completionRanges.contains(model)) {
1010         return m_completionRanges[model].range;
1011     } else {
1012         return nullptr;
1013     }
1014 }
1015 
1016 QMap<KTextEditor::CodeCompletionModel *, KateCompletionWidget::CompletionRange> KateCompletionWidget::completionRanges() const
1017 {
1018     return m_completionRanges;
1019 }
1020 
1021 void KateCompletionWidget::modelReset()
1022 {
1023     setUpdatesEnabled(false);
1024     m_entryList->setAnimated(false);
1025 
1026     for (int row = 0; row < m_entryList->model()->rowCount(QModelIndex()); ++row) {
1027         QModelIndex index(m_entryList->model()->index(row, 0, QModelIndex()));
1028         if (!m_entryList->isExpanded(index)) {
1029             m_entryList->expand(index);
1030         }
1031     }
1032     setUpdatesEnabled(true);
1033 }
1034 
1035 KateCompletionTree *KateCompletionWidget::treeView() const
1036 {
1037     return m_entryList;
1038 }
1039 
1040 QModelIndex KateCompletionWidget::selectedIndex() const
1041 {
1042     if (!isCompletionActive()) {
1043         return QModelIndex();
1044     }
1045 
1046     return m_entryList->currentIndex();
1047 }
1048 
1049 bool KateCompletionWidget::navigateLeft()
1050 {
1051     m_hadCompletionNavigation = true;
1052     if (currentEmbeddedWidget()) {
1053         QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetLeft");
1054     }
1055 
1056     QModelIndex index = selectedIndex();
1057 
1058     if (index.isValid()) {
1059         index.data(KTextEditor::CodeCompletionModel::AccessibilityPrevious);
1060 
1061         return true;
1062     }
1063     return false;
1064 }
1065 
1066 bool KateCompletionWidget::navigateRight()
1067 {
1068     m_hadCompletionNavigation = true;
1069     if (currentEmbeddedWidget()) { ///@todo post 4.2: Make these slots public interface, or create an interface using virtual functions
1070         QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetRight");
1071     }
1072 
1073     QModelIndex index = selectedIndex();
1074 
1075     if (index.isValid()) {
1076         index.data(KTextEditor::CodeCompletionModel::AccessibilityNext);
1077         return true;
1078     }
1079 
1080     return false;
1081 }
1082 
1083 bool KateCompletionWidget::navigateBack()
1084 {
1085     m_hadCompletionNavigation = true;
1086     if (currentEmbeddedWidget()) {
1087         QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetBack");
1088     }
1089     return false;
1090 }
1091 
1092 void KateCompletionWidget::toggleDocumentation()
1093 {
1094     // user has configured the doc to be always visible
1095     // whenever its available.
1096     if (view()->config()->showDocWithCompletion()) {
1097         return;
1098     }
1099 
1100     if (m_docTip->isVisible()) {
1101         m_hadCompletionNavigation = false;
1102         QTimer::singleShot(400, this, [this] {
1103             // if 400ms later this is not false, it means
1104             // that the user navigated inside the active
1105             // widget in doc tip
1106             if (!m_hadCompletionNavigation) {
1107                 m_docTip->hide();
1108             }
1109         });
1110     } else {
1111         showDocTip(m_entryList->currentIndex());
1112     }
1113 }
1114 
1115 void KateCompletionWidget::showDocTip(const QModelIndex &idx)
1116 {
1117     auto data = idx.data(KTextEditor::CodeCompletionModel::ExpandingWidget);
1118     // No data => hide
1119     if (!data.isValid()) {
1120         m_docTip->hide();
1121         return;
1122     } else if (data.canConvert<QWidget *>()) {
1123         m_docTip->setWidget(data.value<QWidget *>());
1124     } else if (data.canConvert<QString>()) {
1125         QString text = data.toString();
1126         if (text.isEmpty()) {
1127             m_docTip->hide();
1128             return;
1129         }
1130         m_docTip->setText(text);
1131     }
1132 
1133     m_docTip->updatePosition(this);
1134     if (!m_docTip->isVisible()) {
1135         m_docTip->show();
1136     }
1137 }
1138 
1139 bool KateCompletionWidget::eventFilter(QObject *watched, QEvent *event)
1140 {
1141     if (watched != this && event->type() == QEvent::Resize && isCompletionActive()) {
1142         abortCompletion();
1143     } else if (event->type() == QEvent::KeyRelease && isCompletionActive()) {
1144         auto e = static_cast<QKeyEvent *>(event);
1145         if (e->key() == Qt::Key_Left && e->modifiers() == Qt::AltModifier) {
1146             if (navigateLeft()) {
1147                 return true;
1148             }
1149         }
1150         if (e->key() == Qt::Key_Right && e->modifiers() == Qt::AltModifier) {
1151             if (navigateRight()) {
1152                 return true;
1153             }
1154         }
1155         if (e->key() == Qt::Key_Up && e->modifiers() == Qt::AltModifier) {
1156             if (navigateUp()) {
1157                 return true;
1158             }
1159         }
1160         if (e->key() == Qt::Key_Down && e->modifiers() == Qt::AltModifier) {
1161             if (navigateDown()) {
1162                 return true;
1163             }
1164         }
1165         if (e->key() == Qt::Key_Return && e->modifiers() == Qt::AltModifier) {
1166             if (navigateAccept()) {
1167                 return true;
1168             }
1169         }
1170         if (e->key() == Qt::Key_Backspace && e->modifiers() == Qt::AltModifier) {
1171             if (navigateBack()) {
1172                 return true;
1173             }
1174         }
1175     }
1176     return QFrame::eventFilter(watched, event);
1177 }
1178 
1179 bool KateCompletionWidget::navigateDown()
1180 {
1181     m_hadCompletionNavigation = true;
1182     if (m_argumentHintModel->rowCount() > 0) {
1183         m_argumentHintWidget->selectNext();
1184         return true;
1185     } else if (currentEmbeddedWidget()) {
1186         QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetDown");
1187     }
1188     return false;
1189 }
1190 
1191 bool KateCompletionWidget::navigateUp()
1192 {
1193     m_hadCompletionNavigation = true;
1194     if (m_argumentHintModel->rowCount() > 0) {
1195         m_argumentHintWidget->selectPrevious();
1196         return true;
1197     } else if (currentEmbeddedWidget()) {
1198         QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetUp");
1199     }
1200     return false;
1201 }
1202 
1203 QWidget *KateCompletionWidget::currentEmbeddedWidget()
1204 {
1205     return m_docTip->currentWidget();
1206 }
1207 
1208 void KateCompletionWidget::cursorDown()
1209 {
1210     m_entryList->nextCompletion();
1211 }
1212 
1213 void KateCompletionWidget::cursorUp()
1214 {
1215     m_entryList->previousCompletion();
1216 }
1217 
1218 void KateCompletionWidget::pageDown()
1219 {
1220     m_entryList->pageDown();
1221 }
1222 
1223 void KateCompletionWidget::pageUp()
1224 {
1225     m_entryList->pageUp();
1226 }
1227 
1228 void KateCompletionWidget::top()
1229 {
1230     m_entryList->top();
1231 }
1232 
1233 void KateCompletionWidget::bottom()
1234 {
1235     m_entryList->bottom();
1236 }
1237 
1238 void KateCompletionWidget::completionModelReset()
1239 {
1240     KTextEditor::CodeCompletionModel *model = qobject_cast<KTextEditor::CodeCompletionModel *>(sender());
1241     if (!model) {
1242         qCWarning(LOG_KTE) << "bad sender";
1243         return;
1244     }
1245 
1246     if (!m_waitingForReset.contains(model)) {
1247         return;
1248     }
1249 
1250     m_waitingForReset.remove(model);
1251 
1252     if (m_waitingForReset.isEmpty()) {
1253         if (!isCompletionActive()) {
1254             // qCDebug(LOG_KTE) << "all completion-models we waited for are ready. Last one: " << model->objectName();
1255             // Eventually show the completion-list if this was the last model we were waiting for
1256             // Use a queued connection once again to make sure that KateCompletionModel is notified before we are
1257             QMetaObject::invokeMethod(this, "modelContentChanged", Qt::QueuedConnection);
1258         }
1259     }
1260 }
1261 
1262 void KateCompletionWidget::modelDestroyed(QObject *model)
1263 {
1264     m_sourceModels.removeAll(model);
1265     abortCompletion();
1266 }
1267 
1268 void KateCompletionWidget::registerCompletionModel(KTextEditor::CodeCompletionModel *model)
1269 {
1270     if (m_sourceModels.contains(model)) {
1271         return;
1272     }
1273 
1274     connect(model, &KTextEditor::CodeCompletionModel::destroyed, this, &KateCompletionWidget::modelDestroyed);
1275     // This connection must not be queued
1276     connect(model, &KTextEditor::CodeCompletionModel::modelReset, this, &KateCompletionWidget::completionModelReset);
1277 
1278     m_sourceModels.append(model);
1279 
1280     if (isCompletionActive()) {
1281         m_presentationModel->addCompletionModel(model);
1282     }
1283 }
1284 
1285 void KateCompletionWidget::unregisterCompletionModel(KTextEditor::CodeCompletionModel *model)
1286 {
1287     disconnect(model, &KTextEditor::CodeCompletionModel::destroyed, this, &KateCompletionWidget::modelDestroyed);
1288     disconnect(model, &KTextEditor::CodeCompletionModel::modelReset, this, &KateCompletionWidget::completionModelReset);
1289 
1290     m_sourceModels.removeAll(model);
1291     abortCompletion();
1292 }
1293 
1294 bool KateCompletionWidget::isCompletionModelRegistered(KTextEditor::CodeCompletionModel *model) const
1295 {
1296     return m_sourceModels.contains(model);
1297 }
1298 
1299 QList<KTextEditor::CodeCompletionModel *> KateCompletionWidget::codeCompletionModels() const
1300 {
1301     return m_sourceModels;
1302 }
1303 
1304 int KateCompletionWidget::automaticInvocationDelay() const
1305 {
1306     return m_automaticInvocationDelay;
1307 }
1308 
1309 void KateCompletionWidget::setIgnoreBufferSignals(bool ignore) const
1310 {
1311     if (ignore) {
1312         disconnect(view()->doc(), &KTextEditor::Document::lineWrapped, this, &KateCompletionWidget::wrapLine);
1313         disconnect(view()->doc(), &KTextEditor::Document::lineUnwrapped, this, &KateCompletionWidget::unwrapLine);
1314         disconnect(view()->doc(), &KTextEditor::Document::textInserted, this, &KateCompletionWidget::insertText);
1315         disconnect(view()->doc(), &KTextEditor::Document::textRemoved, this, &KateCompletionWidget::removeText);
1316     } else {
1317         connect(view()->doc(), &KTextEditor::Document::lineWrapped, this, &KateCompletionWidget::wrapLine);
1318         connect(view()->doc(), &KTextEditor::Document::lineUnwrapped, this, &KateCompletionWidget::unwrapLine);
1319         connect(view()->doc(), &KTextEditor::Document::textInserted, this, &KateCompletionWidget::insertText);
1320         connect(view()->doc(), &KTextEditor::Document::textRemoved, this, &KateCompletionWidget::removeText);
1321     }
1322 }
1323 
1324 void KateCompletionWidget::setAutomaticInvocationDelay(int delay)
1325 {
1326     m_automaticInvocationDelay = delay;
1327 }
1328 
1329 void KateCompletionWidget::wrapLine(KTextEditor::Document *, KTextEditor::Cursor)
1330 {
1331     m_lastInsertionByUser = !m_completionEditRunning;
1332 
1333     // wrap line, be done
1334     m_automaticInvocationLine.clear();
1335     m_automaticInvocationTimer->stop();
1336 }
1337 
1338 void KateCompletionWidget::unwrapLine(KTextEditor::Document *, int)
1339 {
1340     m_lastInsertionByUser = !m_completionEditRunning;
1341 
1342     // just removal
1343     m_automaticInvocationLine.clear();
1344     m_automaticInvocationTimer->stop();
1345 }
1346 
1347 void KateCompletionWidget::insertText(KTextEditor::Document *, KTextEditor::Cursor position, const QString &text)
1348 {
1349     m_lastInsertionByUser = !m_completionEditRunning;
1350 
1351     // no invoke?
1352     if (!view()->isAutomaticInvocationEnabled()) {
1353         m_automaticInvocationLine.clear();
1354         m_automaticInvocationTimer->stop();
1355         return;
1356     }
1357 
1358     if (m_automaticInvocationAt != position) {
1359         m_automaticInvocationLine.clear();
1360         m_lastInsertionByUser = !m_completionEditRunning;
1361     }
1362 
1363     m_automaticInvocationLine += text;
1364     m_automaticInvocationAt = position;
1365     m_automaticInvocationAt.setColumn(position.column() + text.length());
1366 
1367     if (m_automaticInvocationLine.isEmpty()) {
1368         m_automaticInvocationTimer->stop();
1369         return;
1370     }
1371 
1372     m_automaticInvocationTimer->start(m_automaticInvocationDelay);
1373 }
1374 
1375 void KateCompletionWidget::removeText(KTextEditor::Document *, KTextEditor::Range, const QString &)
1376 {
1377     m_lastInsertionByUser = !m_completionEditRunning;
1378 
1379     // just removal
1380     m_automaticInvocationLine.clear();
1381     m_automaticInvocationTimer->stop();
1382 }
1383 
1384 void KateCompletionWidget::automaticInvocation()
1385 {
1386     // qCDebug(LOG_KTE)<<"m_automaticInvocationAt:"<<m_automaticInvocationAt;
1387     // qCDebug(LOG_KTE)<<view()->cursorPosition();
1388     if (m_automaticInvocationAt != view()->cursorPosition()) {
1389         return;
1390     }
1391 
1392     bool start = false;
1393     QList<KTextEditor::CodeCompletionModel *> models;
1394 
1395     // qCDebug(LOG_KTE)<<"checking models";
1396     for (KTextEditor::CodeCompletionModel *model : std::as_const(m_sourceModels)) {
1397         // qCDebug(LOG_KTE)<<"m_completionRanges contains model?:"<<m_completionRanges.contains(model);
1398         if (m_completionRanges.contains(model)) {
1399             continue;
1400         }
1401 
1402         start = _shouldStartCompletion(model, view(), m_automaticInvocationLine, m_lastInsertionByUser, view()->cursorPosition());
1403         // qCDebug(LOG_KTE)<<"start="<<start;
1404         if (start) {
1405             models << model;
1406         }
1407     }
1408     // qCDebug(LOG_KTE)<<"models found:"<<!models.isEmpty();
1409     if (!models.isEmpty()) {
1410         // Start automatic code completion
1411         startCompletion(KTextEditor::CodeCompletionModel::AutomaticInvocation, models);
1412     }
1413 }
1414 
1415 void KateCompletionWidget::userInvokedCompletion()
1416 {
1417     startCompletion(KTextEditor::CodeCompletionModel::UserInvocation);
1418 }
1419 
1420 void KateCompletionWidget::tabCompletion(Direction direction)
1421 {
1422     m_noAutoHide = true;
1423 
1424     // Not using cursorDown/Up() as we don't want to go into the argument-hint list
1425     if (direction == Down) {
1426         const bool res = m_entryList->nextCompletion();
1427         if (!res) {
1428             m_entryList->top();
1429         }
1430     } else { // direction == Up
1431         const bool res = m_entryList->previousCompletion();
1432         if (!res) {
1433             m_entryList->bottom();
1434         }
1435     }
1436 }
1437 
1438 #include "moc_katecompletionwidget.cpp"