File indexing completed on 2024-04-28 11:44:58
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 ¤tCompletion) 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"