File indexing completed on 2024-04-28 15:30:21

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