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"