File indexing completed on 2024-04-28 15:30:20
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 "katecompletionmodel.h" 0009 0010 #include "kateargumenthintmodel.h" 0011 #include "katecompletiontree.h" 0012 #include "katecompletionwidget.h" 0013 #include "katepartdebug.h" 0014 #include "katerenderer.h" 0015 #include "kateview.h" 0016 #include <ktexteditor/codecompletionmodelcontrollerinterface.h> 0017 0018 #include <KFuzzyMatcher> 0019 #include <KLocalizedString> 0020 0021 #include <QApplication> 0022 #include <QMultiMap> 0023 #include <QTimer> 0024 #include <QVarLengthArray> 0025 0026 using namespace KTextEditor; 0027 0028 /// A helper-class for handling completion-models with hierarchical grouping/optimization 0029 class HierarchicalModelHandler 0030 { 0031 public: 0032 explicit HierarchicalModelHandler(CodeCompletionModel *model); 0033 void addValue(CodeCompletionModel::ExtraItemDataRoles role, const QVariant &value); 0034 // Walks the index upwards and collects all defined completion-roles on the way 0035 void collectRoles(const QModelIndex &index); 0036 void takeRole(const QModelIndex &index); 0037 0038 CodeCompletionModel *model() const; 0039 0040 // Assumes that index is a sub-index of the indices where role-values were taken 0041 QVariant getData(CodeCompletionModel::ExtraItemDataRoles role, const QModelIndex &index) const; 0042 0043 bool hasHierarchicalRoles() const; 0044 0045 int inheritanceDepth(const QModelIndex &i) const; 0046 0047 QString customGroup() const 0048 { 0049 return m_customGroup; 0050 } 0051 0052 int customGroupingKey() const 0053 { 0054 return m_groupSortingKey; 0055 } 0056 0057 private: 0058 typedef QMap<CodeCompletionModel::ExtraItemDataRoles, QVariant> RoleMap; 0059 RoleMap m_roleValues; 0060 QString m_customGroup; 0061 int m_groupSortingKey; 0062 CodeCompletionModel *m_model; 0063 }; 0064 0065 CodeCompletionModel *HierarchicalModelHandler::model() const 0066 { 0067 return m_model; 0068 } 0069 0070 bool HierarchicalModelHandler::hasHierarchicalRoles() const 0071 { 0072 return !m_roleValues.isEmpty(); 0073 } 0074 0075 void HierarchicalModelHandler::collectRoles(const QModelIndex &index) 0076 { 0077 if (index.parent().isValid()) { 0078 collectRoles(index.parent()); 0079 } 0080 if (m_model->rowCount(index) != 0) { 0081 takeRole(index); 0082 } 0083 } 0084 0085 int HierarchicalModelHandler::inheritanceDepth(const QModelIndex &i) const 0086 { 0087 return getData(CodeCompletionModel::InheritanceDepth, i).toInt(); 0088 } 0089 0090 void HierarchicalModelHandler::takeRole(const QModelIndex &index) 0091 { 0092 QVariant v = index.data(CodeCompletionModel::GroupRole); 0093 if (v.isValid() && v.canConvert<int>()) { 0094 QVariant value = index.data(v.toInt()); 0095 if (v.toInt() == Qt::DisplayRole) { 0096 m_customGroup = index.data(Qt::DisplayRole).toString(); 0097 QVariant sortingKey = index.data(CodeCompletionModel::InheritanceDepth); 0098 if (sortingKey.canConvert<int>()) { 0099 m_groupSortingKey = sortingKey.toInt(); 0100 } 0101 } else { 0102 m_roleValues[(CodeCompletionModel::ExtraItemDataRoles)v.toInt()] = value; 0103 } 0104 } else { 0105 qCDebug(LOG_KTE) << "Did not return valid GroupRole in hierarchical completion-model"; 0106 } 0107 } 0108 0109 QVariant HierarchicalModelHandler::getData(CodeCompletionModel::ExtraItemDataRoles role, const QModelIndex &index) const 0110 { 0111 RoleMap::const_iterator it = m_roleValues.find(role); 0112 if (it != m_roleValues.end()) { 0113 return *it; 0114 } else { 0115 return index.data(role); 0116 } 0117 } 0118 0119 HierarchicalModelHandler::HierarchicalModelHandler(CodeCompletionModel *model) 0120 : m_groupSortingKey(-1) 0121 , m_model(model) 0122 { 0123 } 0124 0125 void HierarchicalModelHandler::addValue(CodeCompletionModel::ExtraItemDataRoles role, const QVariant &value) 0126 { 0127 m_roleValues[role] = value; 0128 } 0129 0130 KateCompletionModel::KateCompletionModel(KateCompletionWidget *parent) 0131 : ExpandingWidgetModel(parent) 0132 , m_ungrouped(new Group({}, 0, this)) 0133 , m_argumentHints(new Group(i18n("Argument-hints"), -1, this)) 0134 , m_bestMatches(new Group(i18n("Best matches"), BestMatchesProperty, this)) 0135 { 0136 m_emptyGroups.append(m_ungrouped); 0137 m_emptyGroups.append(m_argumentHints); 0138 m_emptyGroups.append(m_bestMatches); 0139 0140 m_updateBestMatchesTimer = new QTimer(this); 0141 m_updateBestMatchesTimer->setSingleShot(true); 0142 connect(m_updateBestMatchesTimer, &QTimer::timeout, this, &KateCompletionModel::updateBestMatches); 0143 0144 m_groupHash.insert(0, m_ungrouped); 0145 m_groupHash.insert(-1, m_argumentHints); 0146 m_groupHash.insert(BestMatchesProperty, m_argumentHints); 0147 0148 QList<QList<int>> mergedColumns; 0149 mergedColumns << (QList<int>() << 0); 0150 mergedColumns << (QList<int>() << 1 << 2 << 3 << 4); 0151 mergedColumns << (QList<int>() << 5); 0152 m_columnMerges = mergedColumns; 0153 0154 createGroups(); 0155 } 0156 0157 KateCompletionModel::~KateCompletionModel() 0158 { 0159 clearCompletionModels(); 0160 delete m_argumentHints; 0161 delete m_ungrouped; 0162 delete m_bestMatches; 0163 } 0164 0165 QTreeView *KateCompletionModel::treeView() const 0166 { 0167 return view()->completionWidget()->treeView(); 0168 } 0169 0170 QVariant KateCompletionModel::data(const QModelIndex &index, int role) const 0171 { 0172 if (!hasCompletionModel() || !index.isValid()) { 0173 return QVariant(); 0174 } 0175 0176 if (role == InternalRole::IsNonEmptyGroup) { 0177 auto group = groupForIndex(index); 0178 return group && !group->isEmpty; 0179 } 0180 0181 // groupOfParent returns a group when the index is a member of that group, but not the group head/label. 0182 if (!hasGroups() || groupOfParent(index)) { 0183 if (role == Qt::TextAlignmentRole) { 0184 int c = 0; 0185 for (const QList<int> &list : std::as_const(m_columnMerges)) { 0186 if (index.column() < c + list.size()) { 0187 c += list.size(); 0188 continue; 0189 } else if (list.count() == 1 && list.first() == CodeCompletionModel::Scope) { 0190 return Qt::AlignRight; 0191 } else { 0192 return QVariant(); 0193 } 0194 } 0195 } 0196 0197 // Merge text for column merging 0198 if (role == Qt::DisplayRole) { 0199 QString text; 0200 for (int column : m_columnMerges[index.column()]) { 0201 QModelIndex sourceIndex = mapToSource(createIndex(index.row(), column, index.internalPointer())); 0202 text.append(sourceIndex.data(role).toString()); 0203 } 0204 0205 return text; 0206 } 0207 0208 if (role == CodeCompletionModel::HighlightingMethod) { 0209 // Return that we are doing custom-highlighting of one of the sub-strings does it. Unfortunately internal highlighting does not work for the other 0210 // substrings. 0211 for (int column : m_columnMerges[index.column()]) { 0212 QModelIndex sourceIndex = mapToSource(createIndex(index.row(), column, index.internalPointer())); 0213 QVariant method = sourceIndex.data(CodeCompletionModel::HighlightingMethod); 0214 if (method.type() == QVariant::Int && method.toInt() == CodeCompletionModel::CustomHighlighting) { 0215 return QVariant(CodeCompletionModel::CustomHighlighting); 0216 } 0217 } 0218 return QVariant(); 0219 } 0220 if (role == CodeCompletionModel::CustomHighlight) { 0221 // Merge custom highlighting if multiple columns were merged 0222 QStringList strings; 0223 0224 // Collect strings 0225 const auto &columns = m_columnMerges[index.column()]; 0226 strings.reserve(columns.size()); 0227 for (int column : columns) { 0228 strings << mapToSource(createIndex(index.row(), column, index.internalPointer())).data(Qt::DisplayRole).toString(); 0229 } 0230 0231 QList<QVariantList> highlights; 0232 0233 // Collect custom-highlightings 0234 highlights.reserve(columns.size()); 0235 for (int column : columns) { 0236 highlights << mapToSource(createIndex(index.row(), column, index.internalPointer())).data(CodeCompletionModel::CustomHighlight).toList(); 0237 } 0238 0239 return mergeCustomHighlighting(strings, highlights, 0); 0240 } 0241 0242 QVariant v = mapToSource(index).data(role); 0243 if (v.isValid()) { 0244 return v; 0245 } else { 0246 return ExpandingWidgetModel::data(index, role); 0247 } 0248 } 0249 0250 // Returns a nonzero group if this index is the head of a group(A Label in the list) 0251 Group *g = groupForIndex(index); 0252 0253 if (g && (!g->isEmpty)) { 0254 switch (role) { 0255 case Qt::DisplayRole: 0256 if (!index.column()) { 0257 return g->title; 0258 } 0259 break; 0260 0261 case Qt::FontRole: 0262 if (!index.column()) { 0263 QFont f = view()->renderer()->currentFont(); 0264 f.setBold(true); 0265 return f; 0266 } 0267 break; 0268 0269 case Qt::ForegroundRole: 0270 return QApplication::palette().toolTipText().color(); 0271 case Qt::BackgroundRole: 0272 return QApplication::palette().toolTipBase().color(); 0273 } 0274 } 0275 0276 return QVariant(); 0277 } 0278 0279 int KateCompletionModel::contextMatchQuality(const QModelIndex &index) const 0280 { 0281 if (!index.isValid()) { 0282 return 0; 0283 } 0284 Group *g = groupOfParent(index); 0285 if (!g || g->filtered.size() < (size_t)index.row()) { 0286 return 0; 0287 } 0288 0289 return contextMatchQuality(g->filtered[index.row()].sourceRow()); 0290 } 0291 0292 int KateCompletionModel::contextMatchQuality(const ModelRow &source) const 0293 { 0294 QModelIndex realIndex = source.second; 0295 0296 int bestMatch = -1; 0297 // Iterate through all argument-hints and find the best match-quality 0298 for (const Item &item : std::as_const(m_argumentHints->filtered)) { 0299 const ModelRow &row(item.sourceRow()); 0300 if (realIndex.model() != row.first) { 0301 continue; // We can only match within the same source-model 0302 } 0303 0304 QModelIndex hintIndex = row.second; 0305 0306 QVariant depth = hintIndex.data(CodeCompletionModel::ArgumentHintDepth); 0307 if (!depth.isValid() || depth.type() != QVariant::Int || depth.toInt() != 1) { 0308 continue; // Only match completion-items to argument-hints of depth 1(the ones the item will be given to as argument) 0309 } 0310 0311 hintIndex.data(CodeCompletionModel::SetMatchContext); 0312 0313 QVariant matchQuality = realIndex.data(CodeCompletionModel::MatchQuality); 0314 if (matchQuality.isValid() && matchQuality.type() == QVariant::Int) { 0315 int m = matchQuality.toInt(); 0316 if (m > bestMatch) { 0317 bestMatch = m; 0318 } 0319 } 0320 } 0321 0322 if (m_argumentHints->filtered.empty()) { 0323 QVariant matchQuality = realIndex.data(CodeCompletionModel::MatchQuality); 0324 if (matchQuality.isValid() && matchQuality.type() == QVariant::Int) { 0325 int m = matchQuality.toInt(); 0326 if (m > bestMatch) { 0327 bestMatch = m; 0328 } 0329 } 0330 } 0331 0332 return bestMatch; 0333 } 0334 0335 Qt::ItemFlags KateCompletionModel::flags(const QModelIndex &index) const 0336 { 0337 if (!hasCompletionModel() || !index.isValid()) { 0338 return Qt::NoItemFlags; 0339 } 0340 0341 if (!hasGroups() || groupOfParent(index)) { 0342 return Qt::ItemIsSelectable | Qt::ItemIsEnabled; 0343 } 0344 0345 return Qt::ItemIsEnabled; 0346 } 0347 0348 KateCompletionWidget *KateCompletionModel::widget() const 0349 { 0350 return static_cast<KateCompletionWidget *>(QObject::parent()); 0351 } 0352 0353 KTextEditor::ViewPrivate *KateCompletionModel::view() const 0354 { 0355 return widget()->view(); 0356 } 0357 0358 int KateCompletionModel::columnCount(const QModelIndex &) const 0359 { 0360 return 3; 0361 } 0362 0363 KateCompletionModel::ModelRow KateCompletionModel::modelRowPair(const QModelIndex &index) 0364 { 0365 return qMakePair(static_cast<CodeCompletionModel *>(const_cast<QAbstractItemModel *>(index.model())), index); 0366 } 0367 0368 bool KateCompletionModel::hasChildren(const QModelIndex &parent) const 0369 { 0370 if (!hasCompletionModel()) { 0371 return false; 0372 } 0373 0374 if (!parent.isValid()) { 0375 if (hasGroups()) { 0376 return true; 0377 } 0378 0379 return !m_ungrouped->filtered.empty(); 0380 } 0381 0382 if (parent.column() != 0) { 0383 return false; 0384 } 0385 0386 if (!hasGroups()) { 0387 return false; 0388 } 0389 0390 if (Group *g = groupForIndex(parent)) { 0391 return !g->filtered.empty(); 0392 } 0393 0394 return false; 0395 } 0396 0397 QModelIndex KateCompletionModel::index(int row, int column, const QModelIndex &parent) const 0398 { 0399 if (row < 0 || column < 0 || column >= columnCount(QModelIndex())) { 0400 return QModelIndex(); 0401 } 0402 0403 if (parent.isValid() || !hasGroups()) { 0404 if (parent.isValid() && parent.column() != 0) { 0405 return QModelIndex(); 0406 } 0407 0408 Group *g = groupForIndex(parent); 0409 0410 if (!g) { 0411 return QModelIndex(); 0412 } 0413 0414 if (row >= (int)g->filtered.size()) { 0415 // qCWarning(LOG_KTE) << "Invalid index requested: row " << row << " beyond individual range in group " << g; 0416 return QModelIndex(); 0417 } 0418 0419 // qCDebug(LOG_KTE) << "Returning index for child " << row << " of group " << g; 0420 return createIndex(row, column, g); 0421 } 0422 0423 if (row >= m_rowTable.count()) { 0424 // qCWarning(LOG_KTE) << "Invalid index requested: row " << row << " beyond group range."; 0425 return QModelIndex(); 0426 } 0427 0428 // qCDebug(LOG_KTE) << "Returning index for group " << m_rowTable[row]; 0429 return createIndex(row, column, quintptr(0)); 0430 } 0431 0432 bool KateCompletionModel::hasIndex(int row, int column, const QModelIndex &parent) const 0433 { 0434 if (row < 0 || column < 0 || column >= columnCount(QModelIndex())) { 0435 return false; 0436 } 0437 0438 if (parent.isValid() || !hasGroups()) { 0439 if (parent.isValid() && parent.column() != 0) { 0440 return false; 0441 } 0442 0443 Group *g = groupForIndex(parent); 0444 0445 if (row >= (int)g->filtered.size()) { 0446 return false; 0447 } 0448 0449 return true; 0450 } 0451 0452 if (row >= m_rowTable.count()) { 0453 return false; 0454 } 0455 0456 return true; 0457 } 0458 0459 QModelIndex KateCompletionModel::indexForRow(Group *g, int row) const 0460 { 0461 if (row < 0 || row >= (int)g->filtered.size()) { 0462 return QModelIndex(); 0463 } 0464 0465 return createIndex(row, 0, g); 0466 } 0467 0468 QModelIndex KateCompletionModel::indexForGroup(Group *g) const 0469 { 0470 if (!hasGroups()) { 0471 return QModelIndex(); 0472 } 0473 0474 int row = m_rowTable.indexOf(g); 0475 0476 if (row == -1) { 0477 return QModelIndex(); 0478 } 0479 0480 return createIndex(row, 0, quintptr(0)); 0481 } 0482 0483 void KateCompletionModel::clearGroups() 0484 { 0485 m_ungrouped->clear(); 0486 m_argumentHints->clear(); 0487 m_bestMatches->clear(); 0488 0489 // Don't bother trying to work out where it is 0490 m_rowTable.removeAll(m_ungrouped); 0491 m_emptyGroups.removeAll(m_ungrouped); 0492 0493 m_rowTable.removeAll(m_argumentHints); 0494 m_emptyGroups.removeAll(m_argumentHints); 0495 0496 m_rowTable.removeAll(m_bestMatches); 0497 m_emptyGroups.removeAll(m_bestMatches); 0498 0499 qDeleteAll(m_rowTable); 0500 qDeleteAll(m_emptyGroups); 0501 m_rowTable.clear(); 0502 m_emptyGroups.clear(); 0503 m_groupHash.clear(); 0504 m_customGroupHash.clear(); 0505 0506 m_emptyGroups.append(m_ungrouped); 0507 m_groupHash.insert(0, m_ungrouped); 0508 0509 m_emptyGroups.append(m_argumentHints); 0510 m_groupHash.insert(-1, m_argumentHints); 0511 0512 m_emptyGroups.append(m_bestMatches); 0513 m_groupHash.insert(BestMatchesProperty, m_bestMatches); 0514 } 0515 0516 KateCompletionModel::GroupSet KateCompletionModel::createItems(const HierarchicalModelHandler &_handler, const QModelIndex &i, bool notifyModel) 0517 { 0518 HierarchicalModelHandler handler(_handler); 0519 GroupSet ret; 0520 QAbstractItemModel *model = handler.model(); 0521 0522 if (model->rowCount(i) == 0) { 0523 // Leaf node, create an item 0524 ret.insert(createItem(handler, i, notifyModel)); 0525 } else { 0526 // Non-leaf node, take the role from the node, and recurse to the sub-nodes 0527 handler.takeRole(i); 0528 for (int a = 0; a < model->rowCount(i); a++) { 0529 ret.merge(createItems(handler, model->index(a, 0, i), notifyModel)); 0530 } 0531 } 0532 0533 return ret; 0534 } 0535 0536 KateCompletionModel::GroupSet KateCompletionModel::deleteItems(const QModelIndex &i) 0537 { 0538 GroupSet ret; 0539 0540 if (i.model()->rowCount(i) == 0) { 0541 // Leaf node, delete the item 0542 Group *g = groupForIndex(mapFromSource(i)); 0543 ret.insert(g); 0544 g->removeItem(ModelRow(const_cast<CodeCompletionModel *>(static_cast<const CodeCompletionModel *>(i.model())), i)); 0545 } else { 0546 // Non-leaf node 0547 for (int a = 0; a < i.model()->rowCount(i); a++) { 0548 ret.merge(deleteItems(i.model()->index(a, 0, i))); 0549 } 0550 } 0551 0552 return ret; 0553 } 0554 0555 void KateCompletionModel::createGroups() 0556 { 0557 beginResetModel(); 0558 // After clearing the model, it has to be reset, else we will be in an invalid state while inserting 0559 // new groups. 0560 clearGroups(); 0561 0562 bool has_groups = false; 0563 GroupSet groups; 0564 for (CodeCompletionModel *sourceModel : std::as_const(m_completionModels)) { 0565 has_groups |= sourceModel->hasGroups(); 0566 for (int i = 0; i < sourceModel->rowCount(); ++i) { 0567 groups.merge(createItems(HierarchicalModelHandler(sourceModel), sourceModel->index(i, 0))); 0568 } 0569 } 0570 0571 // since notifyModel = false above, we just appended the data as is, 0572 // we sort it now 0573 for (auto g : groups) { 0574 // no need to sort prefiltered, it is just the raw dump of everything 0575 // filtered is what gets displayed 0576 // std::sort(g->prefilter.begin(), g->prefilter.end()); 0577 std::sort(g->filtered.begin(), g->filtered.end()); 0578 } 0579 0580 m_hasGroups = has_groups; 0581 0582 // debugStats(); 0583 0584 for (Group *g : std::as_const(m_rowTable)) { 0585 hideOrShowGroup(g); 0586 } 0587 0588 for (Group *g : std::as_const(m_emptyGroups)) { 0589 hideOrShowGroup(g); 0590 } 0591 0592 makeGroupItemsUnique(); 0593 0594 updateBestMatches(); 0595 endResetModel(); 0596 } 0597 0598 KateCompletionModel::Group *KateCompletionModel::createItem(const HierarchicalModelHandler &handler, const QModelIndex &sourceIndex, bool notifyModel) 0599 { 0600 // QModelIndex sourceIndex = sourceModel->index(row, CodeCompletionModel::Name, QModelIndex()); 0601 0602 int completionFlags = handler.getData(CodeCompletionModel::CompletionRole, sourceIndex).toInt(); 0603 0604 int argumentHintDepth = handler.getData(CodeCompletionModel::ArgumentHintDepth, sourceIndex).toInt(); 0605 0606 Group *g; 0607 if (argumentHintDepth) { 0608 g = m_argumentHints; 0609 } else { 0610 QString customGroup = handler.customGroup(); 0611 if (!customGroup.isNull() && m_hasGroups) { 0612 if (m_customGroupHash.contains(customGroup)) { 0613 g = m_customGroupHash[customGroup]; 0614 } else { 0615 g = new Group(customGroup, 0, this); 0616 g->customSortingKey = handler.customGroupingKey(); 0617 m_emptyGroups.append(g); 0618 m_customGroupHash.insert(customGroup, g); 0619 } 0620 } else { 0621 g = fetchGroup(completionFlags, handler.hasHierarchicalRoles()); 0622 } 0623 } 0624 0625 Item item = Item(g != m_argumentHints, this, handler, ModelRow(handler.model(), sourceIndex)); 0626 0627 if (g != m_argumentHints) { 0628 item.match(); 0629 } 0630 0631 g->addItem(item, notifyModel); 0632 0633 return g; 0634 } 0635 0636 void KateCompletionModel::slotRowsInserted(const QModelIndex &parent, int start, int end) 0637 { 0638 HierarchicalModelHandler handler(static_cast<CodeCompletionModel *>(sender())); 0639 if (parent.isValid()) { 0640 handler.collectRoles(parent); 0641 } 0642 0643 GroupSet affectedGroups; 0644 for (int i = start; i <= end; ++i) { 0645 affectedGroups.merge(createItems(handler, handler.model()->index(i, 0, parent), /* notifyModel= */ true)); 0646 } 0647 0648 for (auto g : affectedGroups) { 0649 hideOrShowGroup(g, true); 0650 } 0651 } 0652 0653 void KateCompletionModel::slotRowsRemoved(const QModelIndex &parent, int start, int end) 0654 { 0655 CodeCompletionModel *source = static_cast<CodeCompletionModel *>(sender()); 0656 0657 GroupSet affectedGroups; 0658 for (int i = start; i <= end; ++i) { 0659 QModelIndex index = source->index(i, 0, parent); 0660 affectedGroups.merge(deleteItems(index)); 0661 } 0662 0663 for (auto g : affectedGroups) { 0664 hideOrShowGroup(g, true); 0665 } 0666 } 0667 0668 KateCompletionModel::Group *KateCompletionModel::fetchGroup(int attribute, bool forceGrouping) 0669 { 0670 Q_UNUSED(forceGrouping); 0671 0672 ///@todo use forceGrouping 0673 if (!hasGroups()) { 0674 return m_ungrouped; 0675 } 0676 0677 int groupingAttribute = groupingAttributes(attribute); 0678 // qCDebug(LOG_KTE) << attribute << " " << groupingAttribute; 0679 0680 if (m_groupHash.contains(groupingAttribute)) { 0681 return m_groupHash.value(groupingAttribute); 0682 } 0683 0684 QString st; 0685 QString at; 0686 QString it; 0687 QString title; 0688 0689 if (attribute & KTextEditor::CodeCompletionModel::GlobalScope) { 0690 st = QStringLiteral("Global"); 0691 } else if (attribute & KTextEditor::CodeCompletionModel::NamespaceScope) { 0692 st = QStringLiteral("Namespace"); 0693 } else if (attribute & KTextEditor::CodeCompletionModel::LocalScope) { 0694 st = QStringLiteral("Local"); 0695 } 0696 0697 title = st; 0698 0699 if (attribute & KTextEditor::CodeCompletionModel::Public) { 0700 at = QStringLiteral("Public"); 0701 } else if (attribute & KTextEditor::CodeCompletionModel::Protected) { 0702 at = QStringLiteral("Protected"); 0703 } else if (attribute & KTextEditor::CodeCompletionModel::Private) { 0704 at = QStringLiteral("Private"); 0705 } 0706 0707 if (!at.isEmpty()) { 0708 if (!title.isEmpty()) { 0709 title.append(QLatin1String(", ")); 0710 } 0711 0712 title.append(at); 0713 } 0714 0715 Group *ret = new Group(title, attribute, this); 0716 ret->scope = QString(); 0717 0718 m_emptyGroups.append(ret); 0719 m_groupHash.insert(groupingAttribute, ret); 0720 0721 return ret; 0722 } 0723 0724 bool KateCompletionModel::hasGroups() const 0725 { 0726 // qCDebug(LOG_KTE) << "m_groupHash.size()"<<m_groupHash.size(); 0727 // qCDebug(LOG_KTE) << "m_rowTable.count()"<<m_rowTable.count(); 0728 // We cannot decide whether there is groups easily. The problem: The code-model can 0729 // be populated with a delay from within a background-thread. 0730 // Proper solution: Ask all attached code-models(Through a new interface) whether they want to use grouping, 0731 // and if at least one wants to, return true, else return false. 0732 return m_hasGroups; 0733 } 0734 0735 KateCompletionModel::Group *KateCompletionModel::groupForIndex(const QModelIndex &index) const 0736 { 0737 if (!index.isValid()) { 0738 if (!hasGroups()) { 0739 return m_ungrouped; 0740 } else { 0741 return nullptr; 0742 } 0743 } 0744 0745 if (groupOfParent(index)) { 0746 return nullptr; 0747 } 0748 0749 if (index.row() < 0 || index.row() >= m_rowTable.count()) { 0750 return m_ungrouped; 0751 } 0752 0753 return m_rowTable[index.row()]; 0754 } 0755 0756 QModelIndex KateCompletionModel::parent(const QModelIndex &index) const 0757 { 0758 if (!index.isValid()) { 0759 return QModelIndex(); 0760 } 0761 0762 if (Group *g = groupOfParent(index)) { 0763 if (!hasGroups()) { 0764 Q_ASSERT(g == m_ungrouped); 0765 return QModelIndex(); 0766 } 0767 0768 int row = m_rowTable.indexOf(g); 0769 0770 if (row == -1) { 0771 qCWarning(LOG_KTE) << "Couldn't find parent for index" << index; 0772 return QModelIndex(); 0773 } 0774 0775 return createIndex(row, 0, quintptr(0)); 0776 } 0777 0778 return QModelIndex(); 0779 } 0780 0781 int KateCompletionModel::rowCount(const QModelIndex &parent) const 0782 { 0783 if (!parent.isValid()) { 0784 if (hasGroups()) { 0785 // qCDebug(LOG_KTE) << "Returning row count for toplevel " << m_rowTable.count(); 0786 return m_rowTable.count(); 0787 } else { 0788 // qCDebug(LOG_KTE) << "Returning ungrouped row count for toplevel " << m_ungrouped->filtered.count(); 0789 return m_ungrouped->filtered.size(); 0790 } 0791 } 0792 0793 if (parent.column() > 0) { 0794 // only the first column has children 0795 return 0; 0796 } 0797 0798 Group *g = groupForIndex(parent); 0799 0800 // This is not an error, seems you don't have to check hasChildren() 0801 if (!g) { 0802 return 0; 0803 } 0804 0805 // qCDebug(LOG_KTE) << "Returning row count for group " << g << " as " << g->filtered.count(); 0806 return g->filtered.size(); 0807 } 0808 0809 QModelIndex KateCompletionModel::mapToSource(const QModelIndex &proxyIndex) const 0810 { 0811 if (!proxyIndex.isValid()) { 0812 return QModelIndex(); 0813 } 0814 0815 if (Group *g = groupOfParent(proxyIndex)) { 0816 if (!m_rowTable.contains(g)) { 0817 qWarning() << Q_FUNC_INFO << "Stale proxy index for which there is no group"; 0818 return {}; 0819 } 0820 0821 if (proxyIndex.row() >= 0 && proxyIndex.row() < (int)g->filtered.size()) { 0822 ModelRow source = g->filtered[proxyIndex.row()].sourceRow(); 0823 return source.second.sibling(source.second.row(), proxyIndex.column()); 0824 } else { 0825 qCDebug(LOG_KTE) << "Invalid proxy-index"; 0826 } 0827 } 0828 0829 return QModelIndex(); 0830 } 0831 0832 QModelIndex KateCompletionModel::mapFromSource(const QModelIndex &sourceIndex) const 0833 { 0834 if (!sourceIndex.isValid()) { 0835 return QModelIndex(); 0836 } 0837 0838 if (!hasGroups()) { 0839 return index(m_ungrouped->rowOf(modelRowPair(sourceIndex)), sourceIndex.column(), QModelIndex()); 0840 } 0841 0842 for (Group *g : std::as_const(m_rowTable)) { 0843 int row = g->rowOf(modelRowPair(sourceIndex)); 0844 if (row != -1) { 0845 return index(row, sourceIndex.column(), indexForGroup(g)); 0846 } 0847 } 0848 0849 // Copied from above 0850 for (Group *g : std::as_const(m_emptyGroups)) { 0851 int row = g->rowOf(modelRowPair(sourceIndex)); 0852 if (row != -1) { 0853 return index(row, sourceIndex.column(), indexForGroup(g)); 0854 } 0855 } 0856 0857 return QModelIndex(); 0858 } 0859 0860 void KateCompletionModel::setCurrentCompletion(QMap<KTextEditor::CodeCompletionModel *, QString> currentMatch) 0861 { 0862 beginResetModel(); 0863 0864 m_currentMatch = currentMatch; 0865 0866 if (!hasGroups()) { 0867 changeCompletions(m_ungrouped); 0868 } else { 0869 for (Group *g : std::as_const(m_rowTable)) { 0870 if (g != m_argumentHints) { 0871 changeCompletions(g); 0872 } 0873 } 0874 for (Group *g : std::as_const(m_emptyGroups)) { 0875 if (g != m_argumentHints) { 0876 changeCompletions(g); 0877 } 0878 } 0879 } 0880 0881 // NOTE: best matches are also updated in resort 0882 resort(); 0883 0884 endResetModel(); 0885 } 0886 0887 QString KateCompletionModel::commonPrefixInternal(const QString &forcePrefix) const 0888 { 0889 QString commonPrefix; // isNull() = true 0890 0891 QList<Group *> groups = m_rowTable; 0892 groups += m_ungrouped; 0893 0894 for (Group *g : std::as_const(groups)) { 0895 for (const Item &item : std::as_const(g->filtered)) { 0896 uint startPos = m_currentMatch[item.sourceRow().first].length(); 0897 const QString candidate = item.name().mid(startPos); 0898 0899 if (!candidate.startsWith(forcePrefix)) { 0900 continue; 0901 } 0902 0903 if (commonPrefix.isNull()) { 0904 commonPrefix = candidate; 0905 0906 // Replace QString() prefix with QString(), so we won't initialize it again 0907 if (commonPrefix.isNull()) { 0908 commonPrefix = QString(); // isEmpty() = true, isNull() = false 0909 } 0910 } else { 0911 commonPrefix.truncate(candidate.length()); 0912 0913 for (int a = 0; a < commonPrefix.length(); ++a) { 0914 if (commonPrefix[a] != candidate[a]) { 0915 commonPrefix.truncate(a); 0916 break; 0917 } 0918 } 0919 } 0920 } 0921 } 0922 0923 return commonPrefix; 0924 } 0925 0926 QString KateCompletionModel::commonPrefix(QModelIndex selectedIndex) const 0927 { 0928 QString commonPrefix = commonPrefixInternal(QString()); 0929 0930 if (commonPrefix.isEmpty() && selectedIndex.isValid()) { 0931 Group *g = m_ungrouped; 0932 if (hasGroups()) { 0933 g = groupOfParent(selectedIndex); 0934 } 0935 0936 if (g && selectedIndex.row() < (int)g->filtered.size()) { 0937 // Follow the path of the selected item, finding the next non-empty common prefix 0938 Item item = g->filtered[selectedIndex.row()]; 0939 int matchLength = m_currentMatch[item.sourceRow().first].length(); 0940 commonPrefix = commonPrefixInternal(item.name().mid(matchLength).left(1)); 0941 } 0942 } 0943 0944 return commonPrefix; 0945 } 0946 0947 void KateCompletionModel::changeCompletions(Group *g) 0948 { 0949 // This code determines what of the filtered items still fit 0950 // don't notify the model. The model is notified afterwards through a reset(). 0951 g->filtered.clear(); 0952 std::remove_copy_if(g->prefilter.begin(), g->prefilter.end(), std::back_inserter(g->filtered), [](Item &item) { 0953 return !item.match(); 0954 }); 0955 0956 hideOrShowGroup(g, /*notifyModel=*/false); 0957 } 0958 0959 int KateCompletionModel::Group::orderNumber() const 0960 { 0961 if (this == model->m_ungrouped) { 0962 return 700; 0963 } 0964 0965 if (customSortingKey != -1) { 0966 return customSortingKey; 0967 } 0968 0969 if (attribute & BestMatchesProperty) { 0970 return 1; 0971 } 0972 0973 if (attribute & KTextEditor::CodeCompletionModel::LocalScope) { 0974 return 100; 0975 } else if (attribute & KTextEditor::CodeCompletionModel::Public) { 0976 return 200; 0977 } else if (attribute & KTextEditor::CodeCompletionModel::Protected) { 0978 return 300; 0979 } else if (attribute & KTextEditor::CodeCompletionModel::Private) { 0980 return 400; 0981 } else if (attribute & KTextEditor::CodeCompletionModel::NamespaceScope) { 0982 return 500; 0983 } else if (attribute & KTextEditor::CodeCompletionModel::GlobalScope) { 0984 return 600; 0985 } 0986 0987 return 700; 0988 } 0989 0990 bool KateCompletionModel::Group::orderBefore(Group *other) const 0991 { 0992 return orderNumber() < other->orderNumber(); 0993 } 0994 0995 void KateCompletionModel::hideOrShowGroup(Group *g, bool notifyModel) 0996 { 0997 if (g == m_argumentHints) { 0998 Q_EMIT argumentHintsChanged(); 0999 m_updateBestMatchesTimer->start(200); // We have new argument-hints, so we have new best matches 1000 return; // Never show argument-hints in the normal completion-list 1001 } 1002 1003 if (!g->isEmpty) { 1004 if (g->filtered.empty()) { 1005 // Move to empty group list 1006 g->isEmpty = true; 1007 int row = m_rowTable.indexOf(g); 1008 if (row != -1) { 1009 if (hasGroups() && notifyModel) { 1010 beginRemoveRows(QModelIndex(), row, row); 1011 } 1012 m_rowTable.removeAt(row); 1013 if (hasGroups() && notifyModel) { 1014 endRemoveRows(); 1015 } 1016 m_emptyGroups.append(g); 1017 } else { 1018 qCWarning(LOG_KTE) << "Group " << g << " not found in row table!!"; 1019 } 1020 } 1021 1022 } else { 1023 if (!g->filtered.empty()) { 1024 // Move off empty group list 1025 g->isEmpty = false; 1026 1027 int row = 0; // Find row where to insert 1028 for (int a = 0; a < m_rowTable.count(); a++) { 1029 if (g->orderBefore(m_rowTable[a])) { 1030 row = a; 1031 break; 1032 } 1033 row = a + 1; 1034 } 1035 1036 if (notifyModel) { 1037 if (hasGroups()) { 1038 beginInsertRows(QModelIndex(), row, row); 1039 } else { 1040 beginInsertRows(QModelIndex(), 0, g->filtered.size()); 1041 } 1042 } 1043 m_rowTable.insert(row, g); 1044 if (notifyModel) { 1045 endInsertRows(); 1046 } 1047 m_emptyGroups.removeAll(g); 1048 } 1049 } 1050 } 1051 1052 bool KateCompletionModel::indexIsItem(const QModelIndex &index) const 1053 { 1054 if (!hasGroups()) { 1055 return true; 1056 } 1057 1058 if (groupOfParent(index)) { 1059 return true; 1060 } 1061 1062 return false; 1063 } 1064 1065 void KateCompletionModel::slotModelReset() 1066 { 1067 createGroups(); 1068 1069 // debugStats(); 1070 } 1071 1072 void KateCompletionModel::debugStats() 1073 { 1074 if (!hasGroups()) { 1075 qCDebug(LOG_KTE) << "Model groupless, " << m_ungrouped->filtered.size() << " items."; 1076 } else { 1077 qCDebug(LOG_KTE) << "Model grouped (" << m_rowTable.count() << " groups):"; 1078 for (Group *g : std::as_const(m_rowTable)) { 1079 qCDebug(LOG_KTE) << "Group" << g << "count" << g->filtered.size(); 1080 } 1081 } 1082 } 1083 1084 bool KateCompletionModel::hasCompletionModel() const 1085 { 1086 return !m_completionModels.isEmpty(); 1087 } 1088 1089 int KateCompletionModel::translateColumn(int sourceColumn) const 1090 { 1091 if (m_columnMerges.isEmpty()) { 1092 return sourceColumn; 1093 } 1094 1095 /* Debugging - dump column merge list 1096 1097 QString columnMerge; 1098 for (const QList<int> &list : m_columnMerges) { 1099 columnMerge += '['; 1100 for (int column : list) { 1101 columnMerge += QString::number(column) + QLatin1Char(' '); 1102 } 1103 columnMerge += "] "; 1104 } 1105 1106 qCDebug(LOG_KTE) << k_funcinfo << columnMerge;*/ 1107 1108 int c = 0; 1109 for (const QList<int> &list : m_columnMerges) { 1110 for (int column : list) { 1111 if (column == sourceColumn) { 1112 return c; 1113 } 1114 } 1115 c++; 1116 } 1117 return -1; 1118 } 1119 1120 int KateCompletionModel::groupingAttributes(int attribute) const 1121 { 1122 int ret = 0; 1123 1124 if (countBits(attribute & ScopeTypeMask) > 1) { 1125 qCWarning(LOG_KTE) << "Invalid completion model metadata: more than one scope type modifier provided."; 1126 } 1127 if (attribute & KTextEditor::CodeCompletionModel::GlobalScope) { 1128 ret |= KTextEditor::CodeCompletionModel::GlobalScope; 1129 } else if (attribute & KTextEditor::CodeCompletionModel::NamespaceScope) { 1130 ret |= KTextEditor::CodeCompletionModel::NamespaceScope; 1131 } else if (attribute & KTextEditor::CodeCompletionModel::LocalScope) { 1132 ret |= KTextEditor::CodeCompletionModel::LocalScope; 1133 } 1134 1135 if (countBits(attribute & AccessTypeMask) > 1) { 1136 qCWarning(LOG_KTE) << "Invalid completion model metadata: more than one access type modifier provided."; 1137 } 1138 if (attribute & KTextEditor::CodeCompletionModel::Public) { 1139 ret |= KTextEditor::CodeCompletionModel::Public; 1140 } else if (attribute & KTextEditor::CodeCompletionModel::Protected) { 1141 ret |= KTextEditor::CodeCompletionModel::Protected; 1142 } else if (attribute & KTextEditor::CodeCompletionModel::Private) { 1143 ret |= KTextEditor::CodeCompletionModel::Private; 1144 } 1145 1146 return ret; 1147 } 1148 1149 int KateCompletionModel::countBits(int value) 1150 { 1151 int count = 0; 1152 for (int i = 1; i; i <<= 1) { 1153 if (i & value) { 1154 count++; 1155 } 1156 } 1157 1158 return count; 1159 } 1160 1161 KateCompletionModel::Item::Item(bool doInitialMatch, KateCompletionModel *m, const HierarchicalModelHandler &handler, ModelRow sr) 1162 : model(m) 1163 , m_sourceRow(sr) 1164 , matchCompletion(StartsWithMatch) 1165 , m_haveExactMatch(false) 1166 { 1167 inheritanceDepth = handler.getData(CodeCompletionModel::InheritanceDepth, m_sourceRow.second).toInt(); 1168 m_unimportant = handler.getData(CodeCompletionModel::UnimportantItemRole, m_sourceRow.second).toBool(); 1169 1170 QModelIndex nameSibling = sr.second.sibling(sr.second.row(), CodeCompletionModel::Name); 1171 m_nameColumn = nameSibling.data(Qt::DisplayRole).toString(); 1172 1173 if (doInitialMatch) { 1174 match(); 1175 } 1176 } 1177 1178 bool KateCompletionModel::Item::operator<(const Item &rhs) const 1179 { 1180 int ret = 0; 1181 1182 // qCDebug(LOG_KTE) << c1 << " c/w " << c2 << " -> " << (model->isSortingReverse() ? ret > 0 : ret < 0) << " (" << ret << ")"; 1183 1184 if (m_unimportant && !rhs.m_unimportant) { 1185 return false; 1186 } 1187 1188 if (!m_unimportant && rhs.m_unimportant) { 1189 return true; 1190 } 1191 1192 if (matchCompletion < rhs.matchCompletion) { 1193 // enums are ordered in the order items should be displayed 1194 return true; 1195 } 1196 if (matchCompletion > rhs.matchCompletion) { 1197 return false; 1198 } 1199 1200 ret = inheritanceDepth - rhs.inheritanceDepth; 1201 1202 if (ret == 0) { 1203 auto it = rhs.model->m_currentMatch.constFind(rhs.m_sourceRow.first); 1204 if (it != rhs.model->m_currentMatch.cend()) { 1205 const QString &filter = it.value(); 1206 bool thisStartWithFilter = m_nameColumn.startsWith(filter, Qt::CaseSensitive); 1207 bool rhsStartsWithFilter = rhs.m_nameColumn.startsWith(filter, Qt::CaseSensitive); 1208 1209 if (thisStartWithFilter && !rhsStartsWithFilter) { 1210 return true; 1211 } 1212 if (rhsStartsWithFilter && !thisStartWithFilter) { 1213 return false; 1214 } 1215 } 1216 } 1217 1218 if (ret == 0) { 1219 // Do not use localeAwareCompare, because it is simply too slow for a list of about 1000 items 1220 ret = QString::compare(m_nameColumn, rhs.m_nameColumn, Qt::CaseInsensitive); 1221 } 1222 1223 if (ret == 0) { 1224 // FIXME need to define a better default ordering for multiple model display 1225 ret = m_sourceRow.second.row() - rhs.m_sourceRow.second.row(); 1226 } 1227 1228 return ret < 0; 1229 } 1230 1231 void KateCompletionModel::Group::addItem(const Item &i, bool notifyModel) 1232 { 1233 if (isEmpty) { 1234 notifyModel = false; 1235 } 1236 1237 QModelIndex groupIndex; 1238 if (notifyModel) { 1239 groupIndex = model->indexForGroup(this); 1240 } 1241 1242 if (notifyModel) { 1243 prefilter.insert(std::upper_bound(prefilter.begin(), prefilter.end(), i), i); 1244 } else { 1245 prefilter.push_back(i); 1246 } 1247 1248 if (i.isVisible()) { 1249 if (notifyModel) { 1250 auto it = std::upper_bound(filtered.begin(), filtered.end(), i); 1251 const auto rowNumber = it - filtered.begin(); 1252 model->beginInsertRows(groupIndex, rowNumber, rowNumber); 1253 filtered.insert(it, i); 1254 } else { 1255 // we will sort it later 1256 filtered.push_back(i); 1257 } 1258 } 1259 1260 if (notifyModel) { 1261 model->endInsertRows(); 1262 } 1263 } 1264 1265 bool KateCompletionModel::Group::removeItem(const ModelRow &row) 1266 { 1267 for (size_t pi = 0; pi < prefilter.size(); ++pi) { 1268 if (prefilter[pi].sourceRow() == row) { 1269 int index = rowOf(row); 1270 if (index != -1) { 1271 model->beginRemoveRows(model->indexForGroup(this), index, index); 1272 filtered.erase(filtered.begin() + index); 1273 } 1274 1275 prefilter.erase(prefilter.begin() + pi); 1276 1277 if (index != -1) { 1278 model->endRemoveRows(); 1279 } 1280 1281 return index != -1; 1282 } 1283 } 1284 1285 Q_ASSERT(false); 1286 return false; 1287 } 1288 1289 KateCompletionModel::Group::Group(const QString &title, int attribute, KateCompletionModel *m) 1290 : model(m) 1291 , attribute(attribute) 1292 // ugly hack to add some left margin 1293 , title(QLatin1Char(' ') + title) 1294 , isEmpty(true) 1295 , customSortingKey(-1) 1296 { 1297 Q_ASSERT(model); 1298 } 1299 1300 void KateCompletionModel::Group::resort() 1301 { 1302 std::stable_sort(filtered.begin(), filtered.end()); 1303 model->hideOrShowGroup(this); 1304 } 1305 1306 void KateCompletionModel::resort() 1307 { 1308 for (Group *g : std::as_const(m_rowTable)) { 1309 g->resort(); 1310 } 1311 1312 for (Group *g : std::as_const(m_emptyGroups)) { 1313 g->resort(); 1314 } 1315 1316 // call updateBestMatches here, so they are moved to the top again. 1317 updateBestMatches(); 1318 } 1319 1320 bool KateCompletionModel::Item::isValid() const 1321 { 1322 return model && m_sourceRow.first && m_sourceRow.second.row() >= 0; 1323 } 1324 1325 void KateCompletionModel::Group::clear() 1326 { 1327 prefilter.clear(); 1328 filtered.clear(); 1329 isEmpty = true; 1330 } 1331 1332 uint KateCompletionModel::filteredItemCount() const 1333 { 1334 uint ret = 0; 1335 for (Group *group : m_rowTable) { 1336 ret += group->filtered.size(); 1337 } 1338 1339 return ret; 1340 } 1341 1342 bool KateCompletionModel::shouldMatchHideCompletionList() const 1343 { 1344 // @todo Make this faster 1345 1346 bool doHide = false; 1347 CodeCompletionModel *hideModel = nullptr; 1348 1349 for (Group *group : std::as_const(m_rowTable)) { 1350 for (const Item &item : std::as_const(group->filtered)) { 1351 if (item.haveExactMatch()) { 1352 KTextEditor::CodeCompletionModelControllerInterface *iface3 = 1353 qobject_cast<KTextEditor::CodeCompletionModelControllerInterface *>(item.sourceRow().first); 1354 bool hide = false; 1355 if (!iface3) { 1356 hide = true; 1357 } 1358 if (iface3 1359 && iface3->matchingItem(item.sourceRow().second) == KTextEditor::CodeCompletionModelControllerInterface::HideListIfAutomaticInvocation) { 1360 hide = true; 1361 } 1362 if (hide) { 1363 doHide = true; 1364 hideModel = item.sourceRow().first; 1365 } 1366 } 1367 } 1368 } 1369 1370 if (doHide) { 1371 // Check if all other visible items are from the same model 1372 for (Group *group : std::as_const(m_rowTable)) { 1373 for (const Item &item : std::as_const(group->filtered)) { 1374 if (item.sourceRow().first != hideModel) { 1375 return false; 1376 } 1377 } 1378 } 1379 } 1380 1381 return doHide; 1382 } 1383 1384 static inline QChar toLower(QChar c) 1385 { 1386 return c.isLower() ? c : c.toLower(); 1387 } 1388 1389 bool KateCompletionModel::matchesAbbreviation(const QString &word, const QString &typed, int &score) 1390 { 1391 // A mismatch is very likely for random even for the first letter, 1392 // thus this optimization makes sense. 1393 1394 // We require that first letter must match before we do fuzzy matching. 1395 // Not sure how well this well it works in practice, but seems ok so far. 1396 // Also, 0 might not be the first letter. Some sources add a space or a marker 1397 // at the beginning. So look for first letter 1398 const int firstLetter = [&word] { 1399 for (auto it = word.cbegin(); it != word.cend(); ++it) { 1400 if (it->isLetter()) 1401 return int(it - word.cbegin()); 1402 } 1403 return 0; 1404 }(); 1405 1406 QStringView wordView = word; 1407 wordView = wordView.mid(firstLetter); 1408 1409 if (toLower(wordView.at(0)) != toLower(typed.at(0))) { 1410 return false; 1411 } 1412 1413 const auto res = KFuzzyMatcher::match(typed, wordView); 1414 score = res.score; 1415 return res.matched; 1416 } 1417 1418 static inline bool containsAtWordBeginning(const QString &word, const QString &typed) 1419 { 1420 if (typed.size() > word.size()) { 1421 return false; 1422 } 1423 1424 for (int i = 1; i < word.size(); i++) { 1425 // The current position is a word beginning if the previous character was an underscore 1426 // or if the current character is uppercase. Subsequent uppercase characters do not count, 1427 // to handle the special case of UPPER_CASE_VARS properly. 1428 const QChar c = word.at(i); 1429 const QChar prev = word.at(i - 1); 1430 if (!(prev == QLatin1Char('_') || (c.isUpper() && !prev.isUpper()))) { 1431 continue; 1432 } 1433 if (QStringView(word).mid(i).startsWith(typed, Qt::CaseInsensitive)) { 1434 return true; 1435 } 1436 1437 // If we do not have enough string left, return early 1438 if (word.size() - i < typed.size()) { 1439 return false; 1440 } 1441 } 1442 return false; 1443 } 1444 1445 KateCompletionModel::Item::MatchType KateCompletionModel::Item::match() 1446 { 1447 const QString match = model->currentCompletion(m_sourceRow.first); 1448 1449 m_haveExactMatch = false; 1450 1451 // Hehe, everything matches nothing! (ie. everything matches a blank string) 1452 if (match.isEmpty()) { 1453 return PerfectMatch; 1454 } 1455 if (m_nameColumn.isEmpty()) { 1456 return NoMatch; 1457 } 1458 1459 matchCompletion = (m_nameColumn.startsWith(match) ? StartsWithMatch : NoMatch); 1460 1461 if (matchCompletion == NoMatch && !m_nameColumn.isEmpty() && !match.isEmpty()) { 1462 // if still no match, try abbreviation matching 1463 int score = 0; 1464 if (matchesAbbreviation(m_nameColumn, match, score)) { 1465 inheritanceDepth -= score; 1466 matchCompletion = AbbreviationMatch; 1467 } 1468 } 1469 1470 if (matchCompletion == NoMatch) { 1471 // if no match, try for "contains" 1472 // Only match when the occurrence is at a "word" beginning, marked by 1473 // an underscore or a capital. So Foo matches BarFoo and Bar_Foo, but not barfoo. 1474 // Starting at 1 saves looking at the beginning of the word, that was already checked above. 1475 if (containsAtWordBeginning(m_nameColumn, match)) { 1476 matchCompletion = ContainsMatch; 1477 } 1478 } 1479 1480 if (matchCompletion == NoMatch) { 1481 if (m_nameColumn.contains(match, Qt::CaseInsensitive)) { 1482 matchCompletion = ContainsMatch; 1483 } 1484 } 1485 1486 if (matchCompletion && match.length() == m_nameColumn.length()) { 1487 matchCompletion = PerfectMatch; 1488 m_haveExactMatch = true; 1489 } 1490 1491 return matchCompletion; 1492 } 1493 1494 bool KateCompletionModel::Item::isVisible() const 1495 { 1496 return matchCompletion; 1497 } 1498 1499 const KateCompletionModel::ModelRow &KateCompletionModel::Item::sourceRow() const 1500 { 1501 return m_sourceRow; 1502 } 1503 1504 QString KateCompletionModel::currentCompletion(KTextEditor::CodeCompletionModel *model) const 1505 { 1506 return m_currentMatch.value(model); 1507 } 1508 1509 void KateCompletionModel::addCompletionModel(KTextEditor::CodeCompletionModel *model) 1510 { 1511 if (m_completionModels.contains(model)) { 1512 return; 1513 } 1514 1515 m_completionModels.append(model); 1516 1517 connect(model, &KTextEditor::CodeCompletionModel::rowsInserted, this, &KateCompletionModel::slotRowsInserted); 1518 connect(model, &KTextEditor::CodeCompletionModel::rowsRemoved, this, &KateCompletionModel::slotRowsRemoved); 1519 connect(model, &KTextEditor::CodeCompletionModel::modelReset, this, &KateCompletionModel::slotModelReset); 1520 1521 // This performs the reset 1522 createGroups(); 1523 } 1524 1525 void KateCompletionModel::setCompletionModel(KTextEditor::CodeCompletionModel *model) 1526 { 1527 clearCompletionModels(); 1528 addCompletionModel(model); 1529 } 1530 1531 void KateCompletionModel::setCompletionModels(const QList<KTextEditor::CodeCompletionModel *> &models) 1532 { 1533 // if (m_completionModels == models) 1534 // return; 1535 1536 clearCompletionModels(); 1537 1538 m_completionModels = models; 1539 1540 for (KTextEditor::CodeCompletionModel *model : models) { 1541 connect(model, &KTextEditor::CodeCompletionModel::rowsInserted, this, &KateCompletionModel::slotRowsInserted); 1542 connect(model, &KTextEditor::CodeCompletionModel::rowsRemoved, this, &KateCompletionModel::slotRowsRemoved); 1543 connect(model, &KTextEditor::CodeCompletionModel::modelReset, this, &KateCompletionModel::slotModelReset); 1544 } 1545 1546 // This performs the reset 1547 createGroups(); 1548 } 1549 1550 QList<KTextEditor::CodeCompletionModel *> KateCompletionModel::completionModels() const 1551 { 1552 return m_completionModels; 1553 } 1554 1555 void KateCompletionModel::removeCompletionModel(CodeCompletionModel *model) 1556 { 1557 if (!model || !m_completionModels.contains(model)) { 1558 return; 1559 } 1560 1561 bool willCreateGroups = (m_completionModels.size() - 1) > 0; 1562 1563 if (!willCreateGroups) { 1564 beginResetModel(); 1565 } 1566 m_currentMatch.remove(model); 1567 1568 clearGroups(); 1569 1570 model->disconnect(this); 1571 1572 m_completionModels.removeAll(model); 1573 if (!willCreateGroups) { 1574 endResetModel(); 1575 } 1576 1577 if (willCreateGroups) { 1578 // This performs the reset 1579 createGroups(); 1580 } 1581 } 1582 1583 void KateCompletionModel::makeGroupItemsUnique(bool onlyFiltered) 1584 { 1585 struct FilterItems { 1586 FilterItems(KateCompletionModel &model, const QVector<KTextEditor::CodeCompletionModel *> &needShadowing) 1587 : m_model(model) 1588 , m_needShadowing(needShadowing) 1589 { 1590 } 1591 1592 QHash<QString, CodeCompletionModel *> had; 1593 KateCompletionModel &m_model; 1594 const QVector<KTextEditor::CodeCompletionModel *> &m_needShadowing; 1595 1596 void filter(std::vector<Item> &items) 1597 { 1598 std::vector<Item> temp; 1599 temp.reserve(items.size()); 1600 for (const Item &item : items) { 1601 auto it = had.constFind(item.name()); 1602 if (it != had.constEnd() && *it != item.sourceRow().first && m_needShadowing.contains(item.sourceRow().first)) { 1603 continue; 1604 } 1605 1606 had.insert(item.name(), item.sourceRow().first); 1607 temp.push_back(item); 1608 } 1609 items.swap(temp); 1610 } 1611 1612 void filter(Group *group, bool onlyFiltered) 1613 { 1614 if (group->prefilter.size() == group->filtered.size()) { 1615 // Filter only once 1616 filter(group->filtered); 1617 if (!onlyFiltered) { 1618 group->prefilter = group->filtered; 1619 } 1620 } else { 1621 // Must filter twice 1622 filter(group->filtered); 1623 if (!onlyFiltered) { 1624 filter(group->prefilter); 1625 } 1626 } 1627 1628 if (group->filtered.empty()) { 1629 m_model.hideOrShowGroup(group); 1630 } 1631 } 1632 }; 1633 1634 QVector<KTextEditor::CodeCompletionModel *> needShadowing; 1635 for (KTextEditor::CodeCompletionModel *model : std::as_const(m_completionModels)) { 1636 KTextEditor::CodeCompletionModelControllerInterface *v4 = qobject_cast<KTextEditor::CodeCompletionModelControllerInterface *>(model); 1637 if (v4 && v4->shouldHideItemsWithEqualNames()) { 1638 needShadowing.push_back(model); 1639 } 1640 } 1641 1642 if (needShadowing.isEmpty()) { 1643 return; 1644 } 1645 1646 FilterItems filter(*this, needShadowing); 1647 1648 filter.filter(m_ungrouped, onlyFiltered); 1649 1650 for (Group *group : std::as_const(m_rowTable)) { 1651 filter.filter(group, onlyFiltered); 1652 } 1653 } 1654 1655 // Updates the best-matches group 1656 void KateCompletionModel::updateBestMatches() 1657 { 1658 // We cannot do too many operations here, because they are all executed 1659 // whenever a character is added. Would be nice if we could split the 1660 // operations up somewhat using a timer. 1661 int maxMatches = 300; 1662 1663 m_updateBestMatchesTimer->stop(); 1664 // Maps match-qualities to ModelRows paired together with the BestMatchesCount returned by the items. 1665 typedef QMultiMap<int, QPair<int, ModelRow>> BestMatchMap; 1666 BestMatchMap matches; 1667 1668 if (!hasGroups()) { 1669 // If there is no grouping, just change the order of the items, moving the best matching ones to the front 1670 QMultiMap<int, int> rowsForQuality; 1671 1672 int row = 0; 1673 for (const Item &item : m_ungrouped->filtered) { 1674 ModelRow source = item.sourceRow(); 1675 1676 QVariant v = source.second.data(CodeCompletionModel::BestMatchesCount); 1677 1678 if (v.type() == QVariant::Int && v.toInt() > 0) { 1679 int quality = contextMatchQuality(source); 1680 if (quality > 0) { 1681 rowsForQuality.insert(quality, row); 1682 } 1683 } 1684 1685 ++row; 1686 --maxMatches; 1687 if (maxMatches < 0) { 1688 break; 1689 } 1690 } 1691 1692 if (!rowsForQuality.isEmpty()) { 1693 // Rewrite m_ungrouped->filtered in a new order 1694 QSet<int> movedToFront; 1695 std::vector<Item> newFiltered; 1696 newFiltered.reserve(rowsForQuality.size()); 1697 movedToFront.reserve(rowsForQuality.size()); 1698 for (auto it = rowsForQuality.constBegin(); it != rowsForQuality.constEnd(); ++it) { 1699 newFiltered.push_back(m_ungrouped->filtered[it.value()]); 1700 movedToFront.insert(it.value()); 1701 } 1702 std::reverse(newFiltered.begin(), newFiltered.end()); 1703 1704 int size = m_ungrouped->filtered.size(); 1705 for (int a = 0; a < size; ++a) { 1706 if (!movedToFront.contains(a)) { 1707 newFiltered.push_back(m_ungrouped->filtered[a]); 1708 } 1709 } 1710 m_ungrouped->filtered.swap(newFiltered); 1711 } 1712 return; 1713 } 1714 1715 ///@todo Cache the CodeCompletionModel::BestMatchesCount 1716 for (Group *g : std::as_const(m_rowTable)) { 1717 if (g == m_bestMatches) { 1718 continue; 1719 } 1720 for (int a = 0; a < (int)g->filtered.size(); a++) { 1721 ModelRow source = g->filtered[a].sourceRow(); 1722 1723 QVariant v = source.second.data(CodeCompletionModel::BestMatchesCount); 1724 1725 if (v.type() == QVariant::Int && v.toInt() > 0) { 1726 // Return the best match with any of the argument-hints 1727 1728 int quality = contextMatchQuality(source); 1729 if (quality > 0) { 1730 matches.insert(quality, qMakePair(v.toInt(), g->filtered[a].sourceRow())); 1731 } 1732 --maxMatches; 1733 } 1734 1735 if (maxMatches < 0) { 1736 break; 1737 } 1738 } 1739 if (maxMatches < 0) { 1740 break; 1741 } 1742 } 1743 1744 // Now choose how many of the matches will be taken. This is done with the rule: 1745 // The count of shown best-matches should equal the average count of their BestMatchesCounts 1746 int cnt = 0; 1747 int matchesSum = 0; 1748 BestMatchMap::const_iterator it = matches.constEnd(); 1749 while (it != matches.constBegin()) { 1750 --it; 1751 ++cnt; 1752 matchesSum += (*it).first; 1753 if (cnt > matchesSum / cnt) { 1754 break; 1755 } 1756 } 1757 1758 m_bestMatches->filtered.clear(); 1759 1760 it = matches.constEnd(); 1761 1762 while (it != matches.constBegin() && cnt > 0) { 1763 --it; 1764 --cnt; 1765 1766 m_bestMatches->filtered.push_back(Item(true, this, HierarchicalModelHandler((*it).second.first), (*it).second)); 1767 } 1768 1769 hideOrShowGroup(m_bestMatches); 1770 } 1771 1772 void KateCompletionModel::rowSelected(const QModelIndex & /*row*/) const 1773 { 1774 ///@todo delay this 1775 int rc = widget()->argumentHintModel()->rowCount(QModelIndex()); 1776 if (rc == 0) { 1777 return; 1778 } 1779 1780 // For now, simply update the whole column 0 1781 QModelIndex start = widget()->argumentHintModel()->index(0, 0); 1782 QModelIndex end = widget()->argumentHintModel()->index(rc - 1, 0); 1783 1784 widget()->argumentHintModel()->emitDataChanged(start, end); 1785 } 1786 1787 void KateCompletionModel::clearCompletionModels() 1788 { 1789 if (m_completionModels.isEmpty()) { 1790 return; 1791 } 1792 1793 beginResetModel(); 1794 for (CodeCompletionModel *model : std::as_const(m_completionModels)) { 1795 model->disconnect(this); 1796 } 1797 1798 m_completionModels.clear(); 1799 1800 m_currentMatch.clear(); 1801 1802 clearGroups(); 1803 endResetModel(); 1804 } 1805 1806 #include "moc_katecompletionmodel.cpp"