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"