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