File indexing completed on 2024-04-28 07:46:15
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 ¤tCompletion) 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"