KTextEditor

katecompletionmodel.cpp
1 /* SPDX-License-Identifier: LGPL-2.0-or-later
2 
3  Copyright (C) 2005-2006 Hamish Rodda <[email protected]>
4  Copyright (C) 2007-2008 David Nolden <[email protected]>
5 
6  This library is free software; you can redistribute it and/or
7  modify it under the terms of the GNU Library General Public
8  License as published by the Free Software Foundation; either
9  version 2 of the License, or (at your option) any later version.
10 
11  This library is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  Library General Public License for more details.
15 
16  You should have received a copy of the GNU Library General Public License
17  along with this library; see the file COPYING.LIB. If not, write to
18  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  Boston, MA 02110-1301, USA.
20 */
21 
22 #include "katecompletionmodel.h"
23 
24 #include "kateargumenthintmodel.h"
25 #include "katecompletiondelegate.h"
26 #include "katecompletiontree.h"
27 #include "katecompletionwidget.h"
28 #include "kateconfig.h"
29 #include "katepartdebug.h"
30 #include "katerenderer.h"
31 #include "kateview.h"
32 #include <ktexteditor/codecompletionmodelcontrollerinterface.h>
33 
34 #include <KLocalizedString>
35 
36 #include <QApplication>
37 #include <QMultiMap>
38 #include <QTextEdit>
39 #include <QTimer>
40 #include <QVarLengthArray>
41 
42 using namespace KTextEditor;
43 
45 class HierarchicalModelHandler
46 {
47 public:
48  explicit HierarchicalModelHandler(CodeCompletionModel *model);
49  void addValue(CodeCompletionModel::ExtraItemDataRoles role, const QVariant &value);
50  // Walks the index upwards and collects all defined completion-roles on the way
51  void collectRoles(const QModelIndex &index);
52  void takeRole(const QModelIndex &index);
53 
54  CodeCompletionModel *model() const;
55 
56  // Assumes that index is a sub-index of the indices where role-values were taken
57  QVariant getData(CodeCompletionModel::ExtraItemDataRoles role, const QModelIndex &index) const;
58 
59  bool hasHierarchicalRoles() const;
60 
61  int inheritanceDepth(const QModelIndex &i) const;
62 
63  QString customGroup() const
64  {
65  return m_customGroup;
66  }
67 
68  int customGroupingKey() const
69  {
70  return m_groupSortingKey;
71  }
72 
73 private:
75  RoleMap m_roleValues;
76  QString m_customGroup;
77  int m_groupSortingKey;
78  CodeCompletionModel *m_model;
79 };
80 
81 CodeCompletionModel *HierarchicalModelHandler::model() const
82 {
83  return m_model;
84 }
85 
86 bool HierarchicalModelHandler::hasHierarchicalRoles() const
87 {
88  return !m_roleValues.isEmpty();
89 }
90 
91 void HierarchicalModelHandler::collectRoles(const QModelIndex &index)
92 {
93  if (index.parent().isValid()) {
94  collectRoles(index.parent());
95  }
96  if (m_model->rowCount(index) != 0) {
97  takeRole(index);
98  }
99 }
100 
101 int HierarchicalModelHandler::inheritanceDepth(const QModelIndex &i) const
102 {
103  return getData(CodeCompletionModel::InheritanceDepth, i).toInt();
104 }
105 
106 void HierarchicalModelHandler::takeRole(const QModelIndex &index)
107 {
109  if (v.isValid() && v.canConvert(QVariant::Int)) {
110  QVariant value = index.data(v.toInt());
111  if (v.toInt() == Qt::DisplayRole) {
112  m_customGroup = index.data(Qt::DisplayRole).toString();
114  if (sortingKey.canConvert(QVariant::Int)) {
115  m_groupSortingKey = sortingKey.toInt();
116  }
117  } else {
118  m_roleValues[(CodeCompletionModel::ExtraItemDataRoles)v.toInt()] = value;
119  }
120  } else {
121  qCDebug(LOG_KTE) << "Did not return valid GroupRole in hierarchical completion-model";
122  }
123 }
124 
125 QVariant HierarchicalModelHandler::getData(CodeCompletionModel::ExtraItemDataRoles role, const QModelIndex &index) const
126 {
127  RoleMap::const_iterator it = m_roleValues.find(role);
128  if (it != m_roleValues.end()) {
129  return *it;
130  } else {
131  return index.data(role);
132  }
133 }
134 
135 HierarchicalModelHandler::HierarchicalModelHandler(CodeCompletionModel *model)
136  : m_groupSortingKey(-1)
137  , m_model(model)
138 {
139 }
140 
141 void HierarchicalModelHandler::addValue(CodeCompletionModel::ExtraItemDataRoles role, const QVariant &value)
142 {
143  m_roleValues[role] = value;
144 }
145 
146 KateCompletionModel::KateCompletionModel(KateCompletionWidget *parent)
147  : ExpandingWidgetModel(parent)
148  , m_ungrouped(new Group({}, 0, this))
149  , m_argumentHints(new Group(i18n("Argument-hints"), -1, this))
150  , m_bestMatches(new Group(i18n("Best matches"), BestMatchesProperty, this))
151  , m_filterAttributes(KTextEditor::CodeCompletionModel::NoProperty)
152 
153 {
154  m_emptyGroups.append(m_ungrouped);
155  m_emptyGroups.append(m_argumentHints);
156  m_emptyGroups.append(m_bestMatches);
157 
158  m_updateBestMatchesTimer = new QTimer(this);
159  m_updateBestMatchesTimer->setSingleShot(true);
160  connect(m_updateBestMatchesTimer, SIGNAL(timeout()), this, SLOT(updateBestMatches()));
161 
162  m_groupHash.insert(0, m_ungrouped);
163  m_groupHash.insert(-1, m_argumentHints);
164  m_groupHash.insert(BestMatchesProperty, m_argumentHints);
165 }
166 
167 KateCompletionModel::~KateCompletionModel()
168 {
169  clearCompletionModels();
170  delete m_argumentHints;
171  delete m_ungrouped;
172  delete m_bestMatches;
173 }
174 
175 QTreeView *KateCompletionModel::treeView() const
176 {
177  return view()->completionWidget()->treeView();
178 }
179 
180 QVariant KateCompletionModel::data(const QModelIndex &index, int role) const
181 {
182  if (!hasCompletionModel() || !index.isValid()) {
183  return QVariant();
184  }
185 
186  if (role == Qt::DecorationRole && index.column() == KTextEditor::CodeCompletionModel::Prefix && isExpandable(index)) {
187  cacheIcons();
188 
189  if (!isExpanded(index)) {
190  return QVariant(m_collapsedIcon);
191  } else {
192  return QVariant(m_expandedIcon);
193  }
194  }
195 
196  // groupOfParent returns a group when the index is a member of that group, but not the group head/label.
197  if (!hasGroups() || groupOfParent(index)) {
198  if (role == Qt::TextAlignmentRole) {
199  if (isColumnMergingEnabled() && !m_columnMerges.isEmpty()) {
200  int c = 0;
201  for (const QList<int> &list : qAsConst(m_columnMerges)) {
202  if (index.column() < c + list.size()) {
203  c += list.size();
204  continue;
205  } else if (list.count() == 1 && list.first() == CodeCompletionModel::Scope) {
206  return Qt::AlignRight;
207  } else {
208  return QVariant();
209  }
210  }
211 
212  } else if ((!isColumnMergingEnabled() || m_columnMerges.isEmpty()) && index.column() == CodeCompletionModel::Scope) {
213  return Qt::AlignRight;
214  }
215  }
216 
217  // Merge text for column merging
218  if (role == Qt::DisplayRole && !m_columnMerges.isEmpty() && isColumnMergingEnabled()) {
219  QString text;
220  for (int column : m_columnMerges[index.column()]) {
221  QModelIndex sourceIndex = mapToSource(createIndex(index.row(), column, index.internalPointer()));
222  text.append(sourceIndex.data(role).toString());
223  }
224 
225  return text;
226  }
227 
229  // Return that we are doing custom-highlighting of one of the sub-strings does it. Unfortunately internal highlighting does not work for the other substrings.
230  for (int column : m_columnMerges[index.column()]) {
231  QModelIndex sourceIndex = mapToSource(createIndex(index.row(), column, index.internalPointer()));
233  if (method.type() == QVariant::Int && method.toInt() == CodeCompletionModel::CustomHighlighting) {
234  return QVariant(CodeCompletionModel::CustomHighlighting);
235  }
236  }
237  return QVariant();
238  }
240  // Merge custom highlighting if multiple columns were merged
241  QStringList strings;
242 
243  // Collect strings
244  const auto &columns = m_columnMerges[index.column()];
245  strings.reserve(columns.size());
246  for (int column : columns) {
247  strings << mapToSource(createIndex(index.row(), column, index.internalPointer())).data(Qt::DisplayRole).toString();
248  }
249 
250  QList<QVariantList> highlights;
251 
252  // Collect custom-highlightings
253  highlights.reserve(columns.size());
254  for (int column : columns) {
255  highlights << mapToSource(createIndex(index.row(), column, index.internalPointer())).data(CodeCompletionModel::CustomHighlight).toList();
256  }
257 
258  return mergeCustomHighlighting(strings, highlights, 0);
259  }
260 
261  QVariant v = mapToSource(index).data(role);
262  if (v.isValid()) {
263  return v;
264  } else {
265  return ExpandingWidgetModel::data(index, role);
266  }
267  }
268 
269  // Returns a nonzero group if this index is the head of a group(A Label in the list)
270  Group *g = groupForIndex(index);
271 
272  if (g && (!g->isEmpty)) {
273  switch (role) {
274  case Qt::DisplayRole:
275  if (!index.column()) {
276  return g->title;
277  }
278  break;
279 
280  case Qt::FontRole:
281  if (!index.column()) {
282  QFont f = view()->renderer()->currentFont();
283  f.setBold(true);
284  return f;
285  }
286  break;
287 
288  case Qt::ForegroundRole:
290  case Qt::BackgroundRole:
292  }
293  }
294 
295  return QVariant();
296 }
297 
299 {
300  if (!index.isValid()) {
301  return 0;
302  }
303  Group *g = groupOfParent(index);
304  if (!g || g->filtered.size() < index.row()) {
305  return 0;
306  }
307 
308  return contextMatchQuality(g->filtered[index.row()].sourceRow());
309 }
310 
311 int KateCompletionModel::contextMatchQuality(const ModelRow &source) const
312 {
313  QModelIndex realIndex = source.second;
314 
315  int bestMatch = -1;
316  // Iterate through all argument-hints and find the best match-quality
317  for (const Item &item : qAsConst(m_argumentHints->filtered)) {
318  const ModelRow &row(item.sourceRow());
319  if (realIndex.model() != row.first) {
320  continue; // We can only match within the same source-model
321  }
322 
323  QModelIndex hintIndex = row.second;
324 
326  if (!depth.isValid() || depth.type() != QVariant::Int || depth.toInt() != 1) {
327  continue; // Only match completion-items to argument-hints of depth 1(the ones the item will be given to as argument)
328  }
329 
331 
332  QVariant matchQuality = realIndex.data(CodeCompletionModel::MatchQuality);
333  if (matchQuality.isValid() && matchQuality.type() == QVariant::Int) {
334  int m = matchQuality.toInt();
335  if (m > bestMatch) {
336  bestMatch = m;
337  }
338  }
339  }
340 
341  if (m_argumentHints->filtered.isEmpty()) {
342  QVariant matchQuality = realIndex.data(CodeCompletionModel::MatchQuality);
343  if (matchQuality.isValid() && matchQuality.type() == QVariant::Int) {
344  int m = matchQuality.toInt();
345  if (m > bestMatch) {
346  bestMatch = m;
347  }
348  }
349  }
350 
351  return bestMatch;
352 }
353 
354 Qt::ItemFlags KateCompletionModel::flags(const QModelIndex &index) const
355 {
356  if (!hasCompletionModel() || !index.isValid()) {
357  return Qt::NoItemFlags;
358  }
359 
360  if (!hasGroups() || groupOfParent(index)) {
362  }
363 
364  return Qt::ItemIsEnabled;
365 }
366 
367 KateCompletionWidget *KateCompletionModel::widget() const
368 {
369  return static_cast<KateCompletionWidget *>(QObject::parent());
370 }
371 
372 KTextEditor::ViewPrivate *KateCompletionModel::view() const
373 {
374  return widget()->view();
375 }
376 
377 void KateCompletionModel::setMatchCaseSensitivity(Qt::CaseSensitivity match_cs)
378 {
379  m_matchCaseSensitivity = match_cs;
380  Q_ASSERT(m_exactMatchCaseSensitivity == m_matchCaseSensitivity || m_matchCaseSensitivity == Qt::CaseInsensitive);
381 }
382 
383 void KateCompletionModel::setMatchCaseSensitivity(Qt::CaseSensitivity match_cs, Qt::CaseSensitivity exact_match_cs)
384 {
385  m_matchCaseSensitivity = match_cs;
386  m_exactMatchCaseSensitivity = exact_match_cs;
387  Q_ASSERT(m_exactMatchCaseSensitivity == m_matchCaseSensitivity || m_matchCaseSensitivity == Qt::CaseInsensitive);
388 }
389 
390 int KateCompletionModel::columnCount(const QModelIndex &) const
391 {
392  return isColumnMergingEnabled() && !m_columnMerges.isEmpty() ? m_columnMerges.count() : KTextEditor::CodeCompletionModel::ColumnCount;
393 }
394 
395 KateCompletionModel::ModelRow KateCompletionModel::modelRowPair(const QModelIndex &index) const
396 {
397  return qMakePair(static_cast<CodeCompletionModel *>(const_cast<QAbstractItemModel *>(index.model())), index);
398 }
399 
400 bool KateCompletionModel::hasChildren(const QModelIndex &parent) const
401 {
402  if (!hasCompletionModel()) {
403  return false;
404  }
405 
406  if (!parent.isValid()) {
407  if (hasGroups()) {
408  return true;
409  }
410 
411  return !m_ungrouped->filtered.isEmpty();
412  }
413 
414  if (parent.column() != 0) {
415  return false;
416  }
417 
418  if (!hasGroups()) {
419  return false;
420  }
421 
422  if (Group *g = groupForIndex(parent)) {
423  return !g->filtered.isEmpty();
424  }
425 
426  return false;
427 }
428 
429 QModelIndex KateCompletionModel::index(int row, int column, const QModelIndex &parent) const
430 {
431  if (row < 0 || column < 0 || column >= columnCount(QModelIndex())) {
432  return QModelIndex();
433  }
434 
435  if (parent.isValid() || !hasGroups()) {
436  if (parent.isValid() && parent.column() != 0) {
437  return QModelIndex();
438  }
439 
440  Group *g = groupForIndex(parent);
441 
442  if (!g) {
443  return QModelIndex();
444  }
445 
446  if (row >= g->filtered.count()) {
447  // qCWarning(LOG_KTE) << "Invalid index requested: row " << row << " beyond individual range in group " << g;
448  return QModelIndex();
449  }
450 
451  // qCDebug(LOG_KTE) << "Returning index for child " << row << " of group " << g;
452  return createIndex(row, column, g);
453  }
454 
455  if (row >= m_rowTable.count()) {
456  // qCWarning(LOG_KTE) << "Invalid index requested: row " << row << " beyond group range.";
457  return QModelIndex();
458  }
459 
460  // qCDebug(LOG_KTE) << "Returning index for group " << m_rowTable[row];
461  return createIndex(row, column, quintptr(0));
462 }
463 
464 /*QModelIndex KateCompletionModel::sibling( int row, int column, const QModelIndex & index ) const
465 {
466  if (row < 0 || column < 0 || column >= columnCount(QModelIndex()))
467  return QModelIndex();
468 
469  if (!index.isValid()) {
470  }
471 
472  if (Group* g = groupOfParent(index)) {
473  if (row >= g->filtered.count())
474  return QModelIndex();
475 
476  return createIndex(row, column, g);
477  }
478 
479  if (hasGroups())
480  return QModelIndex();
481 
482  if (row >= m_ungrouped->filtered.count())
483  return QModelIndex();
484 
485  return createIndex(row, column, m_ungrouped);
486 }*/
487 
488 bool KateCompletionModel::hasIndex(int row, int column, const QModelIndex &parent) const
489 {
490  if (row < 0 || column < 0 || column >= columnCount(QModelIndex())) {
491  return false;
492  }
493 
494  if (parent.isValid() || !hasGroups()) {
495  if (parent.isValid() && parent.column() != 0) {
496  return false;
497  }
498 
499  Group *g = groupForIndex(parent);
500 
501  if (row >= g->filtered.count()) {
502  return false;
503  }
504 
505  return true;
506  }
507 
508  if (row >= m_rowTable.count()) {
509  return false;
510  }
511 
512  return true;
513 }
514 
515 QModelIndex KateCompletionModel::indexForRow(Group *g, int row) const
516 {
517  if (row < 0 || row >= g->filtered.count()) {
518  return QModelIndex();
519  }
520 
521  return createIndex(row, 0, g);
522 }
523 
524 QModelIndex KateCompletionModel::indexForGroup(Group *g) const
525 {
526  if (!hasGroups()) {
527  return QModelIndex();
528  }
529 
530  int row = m_rowTable.indexOf(g);
531 
532  if (row == -1) {
533  return QModelIndex();
534  }
535 
536  return createIndex(row, 0, quintptr(0));
537 }
538 
539 void KateCompletionModel::clearGroups()
540 {
541  clearExpanding();
542  m_ungrouped->clear();
543  m_argumentHints->clear();
544  m_bestMatches->clear();
545 
546  // Don't bother trying to work out where it is
547  m_rowTable.removeAll(m_ungrouped);
548  m_emptyGroups.removeAll(m_ungrouped);
549 
550  m_rowTable.removeAll(m_argumentHints);
551  m_emptyGroups.removeAll(m_argumentHints);
552 
553  m_rowTable.removeAll(m_bestMatches);
554  m_emptyGroups.removeAll(m_bestMatches);
555 
556  qDeleteAll(m_rowTable);
557  qDeleteAll(m_emptyGroups);
558  m_rowTable.clear();
559  m_emptyGroups.clear();
560  m_groupHash.clear();
561  m_customGroupHash.clear();
562 
563  m_emptyGroups.append(m_ungrouped);
564  m_groupHash.insert(0, m_ungrouped);
565 
566  m_emptyGroups.append(m_argumentHints);
567  m_groupHash.insert(-1, m_argumentHints);
568 
569  m_emptyGroups.append(m_bestMatches);
570  m_groupHash.insert(BestMatchesProperty, m_bestMatches);
571 }
572 
573 QSet<KateCompletionModel::Group *> KateCompletionModel::createItems(const HierarchicalModelHandler &_handler, const QModelIndex &i, bool notifyModel)
574 {
575  HierarchicalModelHandler handler(_handler);
576  QSet<Group *> ret;
577  QAbstractItemModel *model = handler.model();
578 
579  if (model->rowCount(i) == 0) {
580  // Leaf node, create an item
581  ret.insert(createItem(handler, i, notifyModel));
582  } else {
583  // Non-leaf node, take the role from the node, and recurse to the sub-nodes
584  handler.takeRole(i);
585  for (int a = 0; a < model->rowCount(i); a++) {
586  ret += createItems(handler, model->index(a, 0, i), notifyModel);
587  }
588  }
589 
590  return ret;
591 }
592 
593 QSet<KateCompletionModel::Group *> KateCompletionModel::deleteItems(const QModelIndex &i)
594 {
595  QSet<Group *> ret;
596 
597  if (i.model()->rowCount(i) == 0) {
598  // Leaf node, delete the item
599  Group *g = groupForIndex(mapFromSource(i));
600  ret.insert(g);
601  g->removeItem(ModelRow(const_cast<CodeCompletionModel *>(static_cast<const CodeCompletionModel *>(i.model())), i));
602  } else {
603  // Non-leaf node
604  for (int a = 0; a < i.model()->rowCount(i); a++) {
605  ret += deleteItems(i.model()->index(a, 0, i));
606  }
607  }
608 
609  return ret;
610 }
611 
612 void KateCompletionModel::createGroups()
613 {
614  beginResetModel();
615  // After clearing the model, it has to be reset, else we will be in an invalid state while inserting
616  // new groups.
617  clearGroups();
618 
619  bool has_groups = false;
620  for (CodeCompletionModel *sourceModel : qAsConst(m_completionModels)) {
621  has_groups |= sourceModel->hasGroups();
622  for (int i = 0; i < sourceModel->rowCount(); ++i) {
623  createItems(HierarchicalModelHandler(sourceModel), sourceModel->index(i, 0));
624  }
625  }
626  m_hasGroups = has_groups;
627 
628  // debugStats();
629 
630  for (Group *g : qAsConst(m_rowTable)) {
631  hideOrShowGroup(g);
632  }
633 
634  for (Group *g : qAsConst(m_emptyGroups)) {
635  hideOrShowGroup(g);
636  }
637 
638  makeGroupItemsUnique();
639 
640  updateBestMatches();
641  endResetModel();
642 }
643 
644 KateCompletionModel::Group *KateCompletionModel::createItem(const HierarchicalModelHandler &handler, const QModelIndex &sourceIndex, bool notifyModel)
645 {
646  // QModelIndex sourceIndex = sourceModel->index(row, CodeCompletionModel::Name, QModelIndex());
647 
648  int completionFlags = handler.getData(CodeCompletionModel::CompletionRole, sourceIndex).toInt();
649 
650  // Scope is expensive, should not be used with big models
651  QString scopeIfNeeded = (groupingMethod() & Scope) ? sourceIndex.sibling(sourceIndex.row(), CodeCompletionModel::Scope).data(Qt::DisplayRole).toString() : QString();
652 
653  int argumentHintDepth = handler.getData(CodeCompletionModel::ArgumentHintDepth, sourceIndex).toInt();
654 
655  Group *g;
656  if (argumentHintDepth) {
657  g = m_argumentHints;
658  } else {
659  QString customGroup = handler.customGroup();
660  if (!customGroup.isNull() && m_hasGroups) {
661  if (m_customGroupHash.contains(customGroup)) {
662  g = m_customGroupHash[customGroup];
663  } else {
664  g = new Group(customGroup, 0, this);
665  g->customSortingKey = handler.customGroupingKey();
666  m_emptyGroups.append(g);
667  m_customGroupHash.insert(customGroup, g);
668  }
669  } else {
670  g = fetchGroup(completionFlags, scopeIfNeeded, handler.hasHierarchicalRoles());
671  }
672  }
673 
674  Item item = Item(g != m_argumentHints, this, handler, ModelRow(handler.model(), sourceIndex));
675 
676  if (g != m_argumentHints) {
677  item.match();
678  }
679 
680  g->addItem(item, notifyModel);
681 
682  return g;
683 }
684 
685 void KateCompletionModel::slotRowsInserted(const QModelIndex &parent, int start, int end)
686 {
687  QSet<Group *> affectedGroups;
688 
689  HierarchicalModelHandler handler(static_cast<CodeCompletionModel *>(sender()));
690  if (parent.isValid()) {
691  handler.collectRoles(parent);
692  }
693 
694  for (int i = start; i <= end; ++i) {
695  affectedGroups += createItems(handler, handler.model()->index(i, 0, parent), true);
696  }
697 
698  for (Group *g : qAsConst(affectedGroups)) {
699  hideOrShowGroup(g, true);
700  }
701 }
702 
703 void KateCompletionModel::slotRowsRemoved(const QModelIndex &parent, int start, int end)
704 {
705  CodeCompletionModel *source = static_cast<CodeCompletionModel *>(sender());
706 
707  QSet<Group *> affectedGroups;
708 
709  for (int i = start; i <= end; ++i) {
710  QModelIndex index = source->index(i, 0, parent);
711 
712  affectedGroups += deleteItems(index);
713  }
714 
715  for (Group *g : qAsConst(affectedGroups)) {
716  hideOrShowGroup(g, true);
717  }
718 }
719 
720 KateCompletionModel::Group *KateCompletionModel::fetchGroup(int attribute, const QString &scope, bool forceGrouping)
721 {
722  Q_UNUSED(forceGrouping);
723 
725  if (!hasGroups()) {
726  return m_ungrouped;
727  }
728 
729  int groupingAttribute = groupingAttributes(attribute);
730  // qCDebug(LOG_KTE) << attribute << " " << groupingAttribute;
731 
732  if (m_groupHash.contains(groupingAttribute)) {
733  if (groupingMethod() & Scope) {
734  for (QHash<int, Group *>::ConstIterator it = m_groupHash.constFind(groupingAttribute); it != m_groupHash.constEnd() && it.key() == groupingAttribute; ++it)
735  if (it.value()->scope == scope) {
736  return it.value();
737  }
738  } else {
739  return m_groupHash.value(groupingAttribute);
740  }
741  }
742 
743  QString st, at, it;
744  QString title;
745 
746  if (groupingMethod() & ScopeType) {
747  if (attribute & KTextEditor::CodeCompletionModel::GlobalScope) {
748  st = QStringLiteral("Global");
749  } else if (attribute & KTextEditor::CodeCompletionModel::NamespaceScope) {
750  st = QStringLiteral("Namespace");
751  } else if (attribute & KTextEditor::CodeCompletionModel::LocalScope) {
752  st = QStringLiteral("Local");
753  }
754 
755  title = st;
756  }
757 
758  if (groupingMethod() & Scope) {
759  if (!title.isEmpty()) {
760  title.append(QLatin1Char(' '));
761  }
762 
763  title.append(scope);
764  }
765 
766  if (groupingMethod() & AccessType) {
767  if (attribute & KTextEditor::CodeCompletionModel::Public) {
768  at = QStringLiteral("Public");
769  } else if (attribute & KTextEditor::CodeCompletionModel::Protected) {
770  at = QStringLiteral("Protected");
771  } else if (attribute & KTextEditor::CodeCompletionModel::Private) {
772  at = QStringLiteral("Private");
773  }
774 
775  if (accessIncludeStatic() && attribute & KTextEditor::CodeCompletionModel::Static) {
776  at.append(QLatin1String(" Static"));
777  }
778 
779  if (accessIncludeConst() && attribute & KTextEditor::CodeCompletionModel::Const) {
780  at.append(QLatin1String(" Const"));
781  }
782 
783  if (!at.isEmpty()) {
784  if (!title.isEmpty()) {
785  title.append(QLatin1String(", "));
786  }
787 
788  title.append(at);
789  }
790  }
791 
792  if (groupingMethod() & ItemType) {
793  if (attribute & CodeCompletionModel::Namespace) {
794  it = i18n("Namespaces");
795  } else if (attribute & CodeCompletionModel::Class) {
796  it = i18n("Classes");
797  } else if (attribute & CodeCompletionModel::Struct) {
798  it = i18n("Structs");
799  } else if (attribute & CodeCompletionModel::Union) {
800  it = i18n("Unions");
801  } else if (attribute & CodeCompletionModel::Function) {
802  it = i18n("Functions");
803  } else if (attribute & CodeCompletionModel::Variable) {
804  it = i18n("Variables");
805  } else if (attribute & CodeCompletionModel::Enum) {
806  it = i18n("Enumerations");
807  }
808 
809  if (!it.isEmpty()) {
810  if (!title.isEmpty()) {
811  title.append(QLatin1Char(' '));
812  }
813 
814  title.append(it);
815  }
816  }
817 
818  Group *ret = new Group(title, attribute, this);
819  ret->scope = scope;
820 
821  m_emptyGroups.append(ret);
822  m_groupHash.insert(groupingAttribute, ret);
823 
824  return ret;
825 }
826 
827 bool KateCompletionModel::hasGroups() const
828 {
829  // qCDebug(LOG_KTE) << "m_groupHash.size()"<<m_groupHash.size();
830  // qCDebug(LOG_KTE) << "m_rowTable.count()"<<m_rowTable.count();
831  // We cannot decide whether there is groups easily. The problem: The code-model can
832  // be populated with a delay from within a background-thread.
833  // Proper solution: Ask all attached code-models(Through a new interface) whether they want to use grouping,
834  // and if at least one wants to, return true, else return false.
835  return m_groupingEnabled && m_hasGroups;
836 }
837 
838 KateCompletionModel::Group *KateCompletionModel::groupForIndex(const QModelIndex &index) const
839 {
840  if (!index.isValid()) {
841  if (!hasGroups()) {
842  return m_ungrouped;
843  } else {
844  return nullptr;
845  }
846  }
847 
848  if (groupOfParent(index)) {
849  return nullptr;
850  }
851 
852  if (index.row() < 0 || index.row() >= m_rowTable.count()) {
853  return m_ungrouped;
854  }
855 
856  return m_rowTable[index.row()];
857 }
858 
859 /*QMap< int, QVariant > KateCompletionModel::itemData( const QModelIndex & index ) const
860 {
861  if (!hasGroups() || groupOfParent(index)) {
862  QModelIndex index = mapToSource(index);
863  if (index.isValid())
864  return index.model()->itemData(index);
865  }
866 
867  return QAbstractItemModel::itemData(index);
868 }*/
869 
871 {
872  if (!index.isValid()) {
873  return QModelIndex();
874  }
875 
876  if (Group *g = groupOfParent(index)) {
877  if (!hasGroups()) {
878  Q_ASSERT(g == m_ungrouped);
879  return QModelIndex();
880  }
881 
882  int row = m_rowTable.indexOf(g);
883 
884  if (row == -1) {
885  qCWarning(LOG_KTE) << "Couldn't find parent for index" << index;
886  return QModelIndex();
887  }
888 
889  return createIndex(row, 0, quintptr(0));
890  }
891 
892  return QModelIndex();
893 }
894 
895 int KateCompletionModel::rowCount(const QModelIndex &parent) const
896 {
897  if (!parent.isValid()) {
898  if (hasGroups()) {
899  // qCDebug(LOG_KTE) << "Returning row count for toplevel " << m_rowTable.count();
900  return m_rowTable.count();
901  } else {
902  // qCDebug(LOG_KTE) << "Returning ungrouped row count for toplevel " << m_ungrouped->filtered.count();
903  return m_ungrouped->filtered.count();
904  }
905  }
906 
907  if (parent.column() > 0) {
908  // only the first column has children
909  return 0;
910  }
911 
912  Group *g = groupForIndex(parent);
913 
914  // This is not an error, seems you don't have to check hasChildren()
915  if (!g) {
916  return 0;
917  }
918 
919  // qCDebug(LOG_KTE) << "Returning row count for group " << g << " as " << g->filtered.count();
920  return g->filtered.count();
921 }
922 
923 void KateCompletionModel::sort(int column, Qt::SortOrder order)
924 {
925  Q_UNUSED(column)
926  Q_UNUSED(order)
927 }
928 
930 {
931  if (!proxyIndex.isValid()) {
932  return QModelIndex();
933  }
934 
935  if (Group *g = groupOfParent(proxyIndex)) {
936  if (proxyIndex.row() >= 0 && proxyIndex.row() < g->filtered.count()) {
937  ModelRow source = g->filtered[proxyIndex.row()].sourceRow();
938  return source.second.sibling(source.second.row(), proxyIndex.column());
939  } else {
940  qCDebug(LOG_KTE) << "Invalid proxy-index";
941  }
942  }
943 
944  return QModelIndex();
945 }
946 
948 {
949  if (!sourceIndex.isValid()) {
950  return QModelIndex();
951  }
952 
953  if (!hasGroups()) {
954  return index(m_ungrouped->rowOf(modelRowPair(sourceIndex)), sourceIndex.column(), QModelIndex());
955  }
956 
957  for (Group *g : qAsConst(m_rowTable)) {
958  int row = g->rowOf(modelRowPair(sourceIndex));
959  if (row != -1) {
960  return index(row, sourceIndex.column(), indexForGroup(g));
961  }
962  }
963 
964  // Copied from above
965  for (Group *g : qAsConst(m_emptyGroups)) {
966  int row = g->rowOf(modelRowPair(sourceIndex));
967  if (row != -1) {
968  return index(row, sourceIndex.column(), indexForGroup(g));
969  }
970  }
971 
972  return QModelIndex();
973 }
974 
975 void KateCompletionModel::setCurrentCompletion(KTextEditor::CodeCompletionModel *model, const QString &completion)
976 {
977  if (m_currentMatch[model] == completion) {
978  return;
979  }
980 
981  if (!hasCompletionModel()) {
982  m_currentMatch[model] = completion;
983  return;
984  }
985 
986  changeTypes changeType = Change;
987 
988  if (m_currentMatch[model].length() > completion.length() && m_currentMatch[model].startsWith(completion, m_matchCaseSensitivity)) {
989  // Filter has been broadened
990  changeType = Broaden;
991 
992  } else if (m_currentMatch[model].length() < completion.length() && completion.startsWith(m_currentMatch[model], m_matchCaseSensitivity)) {
993  // Filter has been narrowed
994  changeType = Narrow;
995  }
996 
997  // qCDebug(LOG_KTE) << model << "Old match: " << m_currentMatch[model] << ", new: " << completion << ", type: " << changeType;
998 
999  m_currentMatch[model] = completion;
1000 
1001  const bool resetModel = (changeType != Narrow);
1002  if (resetModel) {
1003  beginResetModel();
1004  }
1005 
1006  if (!hasGroups()) {
1007  changeCompletions(m_ungrouped, changeType, !resetModel);
1008  } else {
1009  for (Group *g : qAsConst(m_rowTable)) {
1010  if (g != m_argumentHints) {
1011  changeCompletions(g, changeType, !resetModel);
1012  }
1013  }
1014  for (Group *g : qAsConst(m_emptyGroups)) {
1015  if (g != m_argumentHints) {
1016  changeCompletions(g, changeType, !resetModel);
1017  }
1018  }
1019  }
1020 
1021  // NOTE: best matches are also updated in resort
1022  resort();
1023 
1024  if (resetModel) {
1025  endResetModel();
1026  }
1027 
1028  clearExpanding(); // We need to do this, or be aware of expanding-widgets while filtering.
1029 
1030  emit layoutChanged();
1031 }
1032 
1033 QString KateCompletionModel::commonPrefixInternal(const QString &forcePrefix) const
1034 {
1035  QString commonPrefix; // isNull() = true
1036 
1037  QList<Group *> groups = m_rowTable;
1038  groups += m_ungrouped;
1039 
1040  for (Group *g : qAsConst(groups)) {
1041  for (const Item &item : qAsConst(g->filtered)) {
1042  uint startPos = m_currentMatch[item.sourceRow().first].length();
1043  const QString candidate = item.name().mid(startPos);
1044 
1045  if (!candidate.startsWith(forcePrefix)) {
1046  continue;
1047  }
1048 
1049  if (commonPrefix.isNull()) {
1050  commonPrefix = candidate;
1051 
1052  // Replace QString::null prefix with QString(), so we won't initialize it again
1053  if (commonPrefix.isNull()) {
1054  commonPrefix = QString(); // isEmpty() = true, isNull() = false
1055  }
1056  } else {
1057  commonPrefix.truncate(candidate.length());
1058 
1059  for (int a = 0; a < commonPrefix.length(); ++a) {
1060  if (commonPrefix[a] != candidate[a]) {
1061  commonPrefix.truncate(a);
1062  break;
1063  }
1064  }
1065  }
1066  }
1067  }
1068 
1069  return commonPrefix;
1070 }
1071 
1073 {
1074  QString commonPrefix = commonPrefixInternal(QString());
1075 
1076  if (commonPrefix.isEmpty() && selectedIndex.isValid()) {
1077  Group *g = m_ungrouped;
1078  if (hasGroups()) {
1079  g = groupOfParent(selectedIndex);
1080  }
1081 
1082  if (g && selectedIndex.row() < g->filtered.size()) {
1083  // Follow the path of the selected item, finding the next non-empty common prefix
1084  Item item = g->filtered[selectedIndex.row()];
1085  int matchLength = m_currentMatch[item.sourceRow().first].length();
1086  commonPrefix = commonPrefixInternal(item.name().mid(matchLength).left(1));
1087  }
1088  }
1089 
1090  return commonPrefix;
1091 }
1092 
1093 void KateCompletionModel::changeCompletions(Group *g, changeTypes changeType, bool notifyModel)
1094 {
1095  if (changeType != Narrow) {
1096  g->filtered = g->prefilter;
1097  // In the "Broaden" or "Change" case, just re-filter everything,
1098  // and don't notify the model. The model is notified afterwards through a reset().
1099  }
1100 
1101  // This code determines what of the filtered items still fit, and computes the ranges that were removed, giving
1102  // them to beginRemoveRows(..) in batches
1103 
1105  int deleteUntil = -1; // In each state, the range [currentRow+1, deleteUntil] needs to be deleted
1106  for (int currentRow = g->filtered.count() - 1; currentRow >= 0; --currentRow) {
1107  if (g->filtered[currentRow].match()) {
1108  // This row does not need to be deleted, which means that currentRow+1 to deleteUntil need to be deleted now
1109  if (deleteUntil != -1 && notifyModel) {
1110  beginRemoveRows(indexForGroup(g), currentRow + 1, deleteUntil);
1111  endRemoveRows();
1112  }
1113  deleteUntil = -1;
1114 
1115  newFiltered.prepend(g->filtered[currentRow]);
1116  } else {
1117  if (deleteUntil == -1) {
1118  deleteUntil = currentRow; // Mark that this row needs to be deleted
1119  }
1120  }
1121  }
1122 
1123  if (deleteUntil != -1 && notifyModel) {
1124  beginRemoveRows(indexForGroup(g), 0, deleteUntil);
1125  endRemoveRows();
1126  }
1127 
1128  g->filtered = newFiltered;
1129  hideOrShowGroup(g, notifyModel);
1130 }
1131 
1132 int KateCompletionModel::Group::orderNumber() const
1133 {
1134  if (this == model->m_ungrouped) {
1135  return 700;
1136  }
1137 
1138  if (customSortingKey != -1) {
1139  return customSortingKey;
1140  }
1141 
1142  if (attribute & BestMatchesProperty) {
1143  return 1;
1144  }
1145 
1146  if (attribute & KTextEditor::CodeCompletionModel::LocalScope) {
1147  return 100;
1148  } else if (attribute & KTextEditor::CodeCompletionModel::Public) {
1149  return 200;
1150  } else if (attribute & KTextEditor::CodeCompletionModel::Protected) {
1151  return 300;
1152  } else if (attribute & KTextEditor::CodeCompletionModel::Private) {
1153  return 400;
1154  } else if (attribute & KTextEditor::CodeCompletionModel::NamespaceScope) {
1155  return 500;
1156  } else if (attribute & KTextEditor::CodeCompletionModel::GlobalScope) {
1157  return 600;
1158  }
1159 
1160  return 700;
1161 }
1162 
1163 bool KateCompletionModel::Group::orderBefore(Group *other) const
1164 {
1165  return orderNumber() < other->orderNumber();
1166 }
1167 
1168 void KateCompletionModel::hideOrShowGroup(Group *g, bool notifyModel)
1169 {
1170  if (g == m_argumentHints) {
1171  emit argumentHintsChanged();
1172  m_updateBestMatchesTimer->start(200); // We have new argument-hints, so we have new best matches
1173  return; // Never show argument-hints in the normal completion-list
1174  }
1175 
1176  if (!g->isEmpty) {
1177  if (g->filtered.isEmpty()) {
1178  // Move to empty group list
1179  g->isEmpty = true;
1180  int row = m_rowTable.indexOf(g);
1181  if (row != -1) {
1182  if (hasGroups() && notifyModel) {
1183  beginRemoveRows(QModelIndex(), row, row);
1184  }
1185  m_rowTable.removeAt(row);
1186  if (hasGroups() && notifyModel) {
1187  endRemoveRows();
1188  }
1189  m_emptyGroups.append(g);
1190  } else {
1191  qCWarning(LOG_KTE) << "Group " << g << " not found in row table!!";
1192  }
1193  }
1194 
1195  } else {
1196  if (!g->filtered.isEmpty()) {
1197  // Move off empty group list
1198  g->isEmpty = false;
1199 
1200  int row = 0; // Find row where to insert
1201  for (int a = 0; a < m_rowTable.count(); a++) {
1202  if (g->orderBefore(m_rowTable[a])) {
1203  row = a;
1204  break;
1205  }
1206  row = a + 1;
1207  }
1208 
1209  if (notifyModel) {
1210  if (hasGroups()) {
1211  beginInsertRows(QModelIndex(), row, row);
1212  } else {
1213  beginInsertRows(QModelIndex(), 0, g->filtered.count());
1214  }
1215  }
1216  m_rowTable.insert(row, g);
1217  if (notifyModel) {
1218  endInsertRows();
1219  }
1220  m_emptyGroups.removeAll(g);
1221  }
1222  }
1223 }
1224 
1226 {
1227  if (!hasGroups()) {
1228  return true;
1229  }
1230 
1231  if (groupOfParent(index)) {
1232  return true;
1233  }
1234 
1235  return false;
1236 }
1237 
1238 void KateCompletionModel::slotModelReset()
1239 {
1240  createGroups();
1241 
1242  // debugStats();
1243 }
1244 
1245 void KateCompletionModel::debugStats()
1246 {
1247  if (!hasGroups()) {
1248  qCDebug(LOG_KTE) << "Model groupless, " << m_ungrouped->filtered.count() << " items.";
1249  } else {
1250  qCDebug(LOG_KTE) << "Model grouped (" << m_rowTable.count() << " groups):";
1251  for (Group *g : qAsConst(m_rowTable)) {
1252  qCDebug(LOG_KTE) << "Group" << g << "count" << g->filtered.count();
1253  }
1254  }
1255 }
1256 
1257 bool KateCompletionModel::hasCompletionModel() const
1258 {
1259  return !m_completionModels.isEmpty();
1260 }
1261 
1262 void KateCompletionModel::setFilteringEnabled(bool enable)
1263 {
1264  if (m_filteringEnabled != enable) {
1265  m_filteringEnabled = enable;
1266  }
1267 }
1268 
1269 void KateCompletionModel::setSortingEnabled(bool enable)
1270 {
1271  if (m_sortingEnabled != enable) {
1272  m_sortingEnabled = enable;
1273  beginResetModel();
1274  resort();
1275  endResetModel();
1276  }
1277 }
1278 
1279 void KateCompletionModel::setGroupingEnabled(bool enable)
1280 {
1281  if (m_groupingEnabled != enable) {
1282  m_groupingEnabled = enable;
1283  }
1284 }
1285 
1286 void KateCompletionModel::setColumnMergingEnabled(bool enable)
1287 {
1288  if (m_columnMergingEnabled != enable) {
1289  m_columnMergingEnabled = enable;
1290  }
1291 }
1292 
1293 bool KateCompletionModel::isColumnMergingEnabled() const
1294 {
1295  return m_columnMergingEnabled;
1296 }
1297 
1298 bool KateCompletionModel::isGroupingEnabled() const
1299 {
1300  return m_groupingEnabled;
1301 }
1302 
1303 bool KateCompletionModel::isFilteringEnabled() const
1304 {
1305  return m_filteringEnabled;
1306 }
1307 
1308 bool KateCompletionModel::isSortingEnabled() const
1309 {
1310  return m_sortingEnabled;
1311 }
1312 
1313 QString KateCompletionModel::columnName(int column)
1314 {
1315  switch (column) {
1316  case KTextEditor::CodeCompletionModel::Prefix:
1317  return i18n("Prefix");
1319  return i18n("Icon");
1320  case KTextEditor::CodeCompletionModel::Scope:
1321  return i18n("Scope");
1322  case KTextEditor::CodeCompletionModel::Name:
1323  return i18n("Name");
1324  case KTextEditor::CodeCompletionModel::Arguments:
1325  return i18n("Arguments");
1326  case KTextEditor::CodeCompletionModel::Postfix:
1327  return i18n("Postfix");
1328  }
1329 
1330  return QString();
1331 }
1332 
1333 const QList<QList<int>> &KateCompletionModel::columnMerges() const
1334 {
1335  return m_columnMerges;
1336 }
1337 
1338 void KateCompletionModel::setColumnMerges(const QList<QList<int>> &columnMerges)
1339 {
1340  beginResetModel();
1341  m_columnMerges = columnMerges;
1342  endResetModel();
1343 }
1344 
1345 int KateCompletionModel::translateColumn(int sourceColumn) const
1346 {
1347  if (m_columnMerges.isEmpty()) {
1348  return sourceColumn;
1349  }
1350 
1351  /* Debugging - dump column merge list
1352 
1353  QString columnMerge;
1354  for (const QList<int> &list : m_columnMerges) {
1355  columnMerge += '[';
1356  for (int column : list) {
1357  columnMerge += QString::number(column) + QLatin1Char(' ');
1358  }
1359  columnMerge += "] ";
1360  }
1361 
1362  qCDebug(LOG_KTE) << k_funcinfo << columnMerge;*/
1363 
1364  int c = 0;
1365  for (const QList<int> &list : m_columnMerges) {
1366  for (int column : list) {
1367  if (column == sourceColumn) {
1368  return c;
1369  }
1370  }
1371  c++;
1372  }
1373  return -1;
1374 }
1375 
1376 int KateCompletionModel::groupingAttributes(int attribute) const
1377 {
1378  int ret = 0;
1379 
1380  if (m_groupingMethod & ScopeType) {
1381  if (countBits(attribute & ScopeTypeMask) > 1) {
1382  qCWarning(LOG_KTE) << "Invalid completion model metadata: more than one scope type modifier provided.";
1383  }
1384 
1385  if (attribute & KTextEditor::CodeCompletionModel::GlobalScope) {
1386  ret |= KTextEditor::CodeCompletionModel::GlobalScope;
1387  } else if (attribute & KTextEditor::CodeCompletionModel::NamespaceScope) {
1388  ret |= KTextEditor::CodeCompletionModel::NamespaceScope;
1389  } else if (attribute & KTextEditor::CodeCompletionModel::LocalScope) {
1390  ret |= KTextEditor::CodeCompletionModel::LocalScope;
1391  }
1392  }
1393 
1394  if (m_groupingMethod & AccessType) {
1395  if (countBits(attribute & AccessTypeMask) > 1) {
1396  qCWarning(LOG_KTE) << "Invalid completion model metadata: more than one access type modifier provided.";
1397  }
1398 
1399  if (attribute & KTextEditor::CodeCompletionModel::Public) {
1400  ret |= KTextEditor::CodeCompletionModel::Public;
1401  } else if (attribute & KTextEditor::CodeCompletionModel::Protected) {
1402  ret |= KTextEditor::CodeCompletionModel::Protected;
1403  } else if (attribute & KTextEditor::CodeCompletionModel::Private) {
1404  ret |= KTextEditor::CodeCompletionModel::Private;
1405  }
1406 
1407  if (accessIncludeStatic() && attribute & KTextEditor::CodeCompletionModel::Static) {
1408  ret |= KTextEditor::CodeCompletionModel::Static;
1409  }
1410 
1411  if (accessIncludeConst() && attribute & KTextEditor::CodeCompletionModel::Const) {
1412  ret |= KTextEditor::CodeCompletionModel::Const;
1413  }
1414  }
1415 
1416  if (m_groupingMethod & ItemType) {
1417  if (countBits(attribute & ItemTypeMask) > 1) {
1418  qCWarning(LOG_KTE) << "Invalid completion model metadata: more than one item type modifier provided.";
1419  }
1420 
1421  if (attribute & KTextEditor::CodeCompletionModel::Namespace) {
1422  ret |= KTextEditor::CodeCompletionModel::Namespace;
1423  } else if (attribute & KTextEditor::CodeCompletionModel::Class) {
1424  ret |= KTextEditor::CodeCompletionModel::Class;
1425  } else if (attribute & KTextEditor::CodeCompletionModel::Struct) {
1426  ret |= KTextEditor::CodeCompletionModel::Struct;
1427  } else if (attribute & KTextEditor::CodeCompletionModel::Union) {
1428  ret |= KTextEditor::CodeCompletionModel::Union;
1429  } else if (attribute & KTextEditor::CodeCompletionModel::Function) {
1430  ret |= KTextEditor::CodeCompletionModel::Function;
1431  } else if (attribute & KTextEditor::CodeCompletionModel::Variable) {
1432  ret |= KTextEditor::CodeCompletionModel::Variable;
1433  } else if (attribute & KTextEditor::CodeCompletionModel::Enum) {
1434  ret |= KTextEditor::CodeCompletionModel::Enum;
1435  }
1436 
1437  /*
1438  if (itemIncludeTemplate() && attribute & KTextEditor::CodeCompletionModel::Template)
1439  ret |= KTextEditor::CodeCompletionModel::Template;*/
1440  }
1441 
1442  return ret;
1443 }
1444 
1445 void KateCompletionModel::setGroupingMethod(GroupingMethods m)
1446 {
1447  m_groupingMethod = m;
1448 
1449  createGroups();
1450 }
1451 
1452 bool KateCompletionModel::accessIncludeConst() const
1453 {
1454  return m_accessConst;
1455 }
1456 
1457 void KateCompletionModel::setAccessIncludeConst(bool include)
1458 {
1459  if (m_accessConst != include) {
1460  m_accessConst = include;
1461 
1462  if (groupingMethod() & AccessType) {
1463  createGroups();
1464  }
1465  }
1466 }
1467 
1468 bool KateCompletionModel::accessIncludeStatic() const
1469 {
1470  return m_accessStatic;
1471 }
1472 
1473 void KateCompletionModel::setAccessIncludeStatic(bool include)
1474 {
1475  if (m_accessStatic != include) {
1476  m_accessStatic = include;
1477 
1478  if (groupingMethod() & AccessType) {
1479  createGroups();
1480  }
1481  }
1482 }
1483 
1484 bool KateCompletionModel::accessIncludeSignalSlot() const
1485 {
1486  return m_accesSignalSlot;
1487 }
1488 
1489 void KateCompletionModel::setAccessIncludeSignalSlot(bool include)
1490 {
1491  if (m_accesSignalSlot != include) {
1492  m_accesSignalSlot = include;
1493 
1494  if (groupingMethod() & AccessType) {
1495  createGroups();
1496  }
1497  }
1498 }
1499 
1500 int KateCompletionModel::countBits(int value) const
1501 {
1502  int count = 0;
1503  for (int i = 1; i; i <<= 1)
1504  if (i & value) {
1505  count++;
1506  }
1507 
1508  return count;
1509 }
1510 
1511 KateCompletionModel::GroupingMethods KateCompletionModel::groupingMethod() const
1512 {
1513  return m_groupingMethod;
1514 }
1515 
1516 bool KateCompletionModel::isSortingByInheritanceDepth() const
1517 {
1518  return m_isSortingByInheritance;
1519 }
1520 void KateCompletionModel::setSortingByInheritanceDepth(bool byInheritance)
1521 {
1522  m_isSortingByInheritance = byInheritance;
1523 }
1524 
1525 bool KateCompletionModel::isSortingAlphabetical() const
1526 {
1527  return m_sortingAlphabetical;
1528 }
1529 
1530 Qt::CaseSensitivity KateCompletionModel::sortingCaseSensitivity() const
1531 {
1532  return m_sortingCaseSensitivity;
1533 }
1534 
1535 KateCompletionModel::Item::Item(bool doInitialMatch, KateCompletionModel *m, const HierarchicalModelHandler &handler, ModelRow sr)
1536  : model(m)
1537  , m_sourceRow(sr)
1538  , matchCompletion(StartsWithMatch)
1539  , matchFilters(true)
1540  , m_haveExactMatch(false)
1541 {
1542  inheritanceDepth = handler.getData(CodeCompletionModel::InheritanceDepth, m_sourceRow.second).toInt();
1543  m_unimportant = handler.getData(CodeCompletionModel::UnimportantItemRole, m_sourceRow.second).toBool();
1544 
1545  QModelIndex nameSibling = sr.second.sibling(sr.second.row(), CodeCompletionModel::Name);
1546  m_nameColumn = nameSibling.data(Qt::DisplayRole).toString();
1547 
1548  if (doInitialMatch) {
1549  filter();
1550  match();
1551  }
1552 }
1553 
1554 bool KateCompletionModel::Item::operator<(const Item &rhs) const
1555 {
1556  int ret = 0;
1557 
1558  // qCDebug(LOG_KTE) << c1 << " c/w " << c2 << " -> " << (model->isSortingReverse() ? ret > 0 : ret < 0) << " (" << ret << ")";
1559 
1560  if (m_unimportant && !rhs.m_unimportant) {
1561  return false;
1562  }
1563 
1564  if (!m_unimportant && rhs.m_unimportant) {
1565  return true;
1566  }
1567 
1568  if (matchCompletion < rhs.matchCompletion) {
1569  // enums are ordered in the order items should be displayed
1570  return true;
1571  }
1572  if (matchCompletion > rhs.matchCompletion) {
1573  return false;
1574  }
1575 
1576  if (ret == 0) {
1577  const QString &filter = rhs.model->currentCompletion(rhs.m_sourceRow.first);
1578  bool thisStartWithFilter = m_nameColumn.startsWith(filter, Qt::CaseSensitive);
1579  bool rhsStartsWithFilter = rhs.m_nameColumn.startsWith(filter, Qt::CaseSensitive);
1580 
1581  if (thisStartWithFilter && !rhsStartsWithFilter) {
1582  return true;
1583  }
1584  if (rhsStartsWithFilter && !thisStartWithFilter) {
1585  return false;
1586  }
1587  }
1588 
1589  if (model->isSortingByInheritanceDepth()) {
1590  ret = inheritanceDepth - rhs.inheritanceDepth;
1591  }
1592 
1593  if (ret == 0 && model->isSortingAlphabetical()) {
1594  // Do not use localeAwareCompare, because it is simply too slow for a list of about 1000 items
1595  ret = QString::compare(m_nameColumn, rhs.m_nameColumn, model->sortingCaseSensitivity());
1596  }
1597 
1598  if (ret == 0) {
1599  // FIXME need to define a better default ordering for multiple model display
1600  ret = m_sourceRow.second.row() - rhs.m_sourceRow.second.row();
1601  }
1602 
1603  return ret < 0;
1604 }
1605 
1606 void KateCompletionModel::Group::addItem(const Item &i, bool notifyModel)
1607 {
1608  if (isEmpty) {
1609  notifyModel = false;
1610  }
1611 
1612  QModelIndex groupIndex;
1613  if (notifyModel) {
1614  groupIndex = model->indexForGroup(this);
1615  }
1616 
1617  if (model->isSortingEnabled()) {
1618  prefilter.insert(std::upper_bound(prefilter.begin(), prefilter.end(), i), i);
1619  if (i.isVisible()) {
1620  QList<Item>::iterator it = std::upper_bound(filtered.begin(), filtered.end(), i);
1621  uint rowNumber = it - filtered.begin();
1622 
1623  if (notifyModel) {
1624  model->beginInsertRows(groupIndex, rowNumber, rowNumber);
1625  }
1626 
1627  filtered.insert(it, i);
1628  }
1629  } else {
1630  if (notifyModel) {
1631  model->beginInsertRows(groupIndex, prefilter.size(), prefilter.size());
1632  }
1633  if (i.isVisible()) {
1634  prefilter.append(i);
1635  }
1636  }
1637 
1638  if (notifyModel) {
1639  model->endInsertRows();
1640  }
1641 }
1642 
1643 bool KateCompletionModel::Group::removeItem(const ModelRow &row)
1644 {
1645  for (int pi = 0; pi < prefilter.count(); ++pi)
1646  if (prefilter[pi].sourceRow() == row) {
1647  int index = rowOf(row);
1648  if (index != -1) {
1649  model->beginRemoveRows(model->indexForGroup(this), index, index);
1650  }
1651 
1652  filtered.removeAt(index);
1653  prefilter.removeAt(pi);
1654 
1655  if (index != -1) {
1656  model->endRemoveRows();
1657  }
1658 
1659  return index != -1;
1660  }
1661 
1662  Q_ASSERT(false);
1663  return false;
1664 }
1665 
1666 KateCompletionModel::Group::Group(const QString &title, int attribute, KateCompletionModel *m)
1667  : model(m)
1668  , attribute(attribute)
1669  // ugly hack to add some left margin
1670  , title(QLatin1Char(' ') + title)
1671  , isEmpty(true)
1672  , customSortingKey(-1)
1673 {
1674  Q_ASSERT(model);
1675 }
1676 
1677 void KateCompletionModel::setSortingAlphabetical(bool alphabetical)
1678 {
1679  if (m_sortingAlphabetical != alphabetical) {
1680  m_sortingAlphabetical = alphabetical;
1681  beginResetModel();
1682  resort();
1683  endResetModel();
1684  }
1685 }
1686 
1687 void KateCompletionModel::Group::resort()
1688 {
1689  std::stable_sort(filtered.begin(), filtered.end());
1690  model->hideOrShowGroup(this);
1691 }
1692 
1693 void KateCompletionModel::setSortingCaseSensitivity(Qt::CaseSensitivity cs)
1694 {
1695  if (m_sortingCaseSensitivity != cs) {
1696  m_sortingCaseSensitivity = cs;
1697  beginResetModel();
1698  resort();
1699  endResetModel();
1700  }
1701 }
1702 
1703 void KateCompletionModel::resort()
1704 {
1705  for (Group *g : qAsConst(m_rowTable)) {
1706  g->resort();
1707  }
1708 
1709  for (Group *g : qAsConst(m_emptyGroups)) {
1710  g->resort();
1711  }
1712 
1713  // call updateBestMatches here, so they are moved to the top again.
1714  updateBestMatches();
1715 }
1716 
1717 bool KateCompletionModel::Item::isValid() const
1718 {
1719  return model && m_sourceRow.first && m_sourceRow.second.row() >= 0;
1720 }
1721 
1722 void KateCompletionModel::Group::clear()
1723 {
1724  prefilter.clear();
1725  filtered.clear();
1726  isEmpty = true;
1727 }
1728 
1729 bool KateCompletionModel::filterContextMatchesOnly() const
1730 {
1731  return m_filterContextMatchesOnly;
1732 }
1733 
1734 void KateCompletionModel::setFilterContextMatchesOnly(bool filter)
1735 {
1736  if (m_filterContextMatchesOnly != filter) {
1737  m_filterContextMatchesOnly = filter;
1738  refilter();
1739  }
1740 }
1741 
1742 bool KateCompletionModel::filterByAttribute() const
1743 {
1744  return m_filterByAttribute;
1745 }
1746 
1747 void KateCompletionModel::setFilterByAttribute(bool filter)
1748 {
1749  if (m_filterByAttribute == filter) {
1750  m_filterByAttribute = filter;
1751  refilter();
1752  }
1753 }
1754 
1755 KTextEditor::CodeCompletionModel::CompletionProperties KateCompletionModel::filterAttributes() const
1756 {
1757  return m_filterAttributes;
1758 }
1759 
1760 void KateCompletionModel::setFilterAttributes(KTextEditor::CodeCompletionModel::CompletionProperties attributes)
1761 {
1762  if (m_filterAttributes == attributes) {
1763  m_filterAttributes = attributes;
1764  refilter();
1765  }
1766 }
1767 
1768 int KateCompletionModel::maximumInheritanceDepth() const
1769 {
1770  return m_maximumInheritanceDepth;
1771 }
1772 
1773 void KateCompletionModel::setMaximumInheritanceDepth(int maxDepth)
1774 {
1775  if (m_maximumInheritanceDepth != maxDepth) {
1776  m_maximumInheritanceDepth = maxDepth;
1777  refilter();
1778  }
1779 }
1780 
1781 void KateCompletionModel::refilter()
1782 {
1783  beginResetModel();
1784  m_ungrouped->refilter();
1785 
1786  for (Group *g : qAsConst(m_rowTable)) {
1787  if (g != m_argumentHints) {
1788  g->refilter();
1789  }
1790  }
1791 
1792  for (Group *g : qAsConst(m_emptyGroups)) {
1793  if (g != m_argumentHints) {
1794  g->refilter();
1795  }
1796  }
1797 
1798  updateBestMatches();
1799 
1800  clearExpanding(); // We need to do this, or be aware of expanding-widgets while filtering.
1801  endResetModel();
1802 }
1803 
1804 void KateCompletionModel::Group::refilter()
1805 {
1806  filtered.clear();
1807  for (const Item &i : qAsConst(prefilter)) {
1808  if (!i.isFiltered()) {
1809  filtered.append(i);
1810  }
1811  }
1812 }
1813 
1814 bool KateCompletionModel::Item::filter()
1815 {
1816  matchFilters = false;
1817 
1818  if (model->isFilteringEnabled()) {
1819  QModelIndex sourceIndex = m_sourceRow.second.sibling(m_sourceRow.second.row(), CodeCompletionModel::Name);
1820 
1821  if (model->filterContextMatchesOnly()) {
1822  QVariant contextMatch = sourceIndex.data(CodeCompletionModel::MatchQuality);
1823  if (contextMatch.canConvert(QVariant::Int) && !contextMatch.toInt()) {
1824  return false;
1825  }
1826  }
1827 
1828  if (model->filterByAttribute()) {
1829  int completionFlags = sourceIndex.data(CodeCompletionModel::CompletionRole).toInt();
1830  if (model->filterAttributes() & completionFlags) {
1831  return false;
1832  }
1833  }
1834 
1835  if (model->maximumInheritanceDepth() > 0) {
1836  int inheritanceDepth = sourceIndex.data(CodeCompletionModel::InheritanceDepth).toInt();
1837  if (inheritanceDepth > model->maximumInheritanceDepth()) {
1838  return false;
1839  }
1840  }
1841  }
1842 
1843  matchFilters = true;
1844  return matchFilters;
1845 }
1846 
1847 uint KateCompletionModel::filteredItemCount() const
1848 {
1849  uint ret = 0;
1850  for (Group *group : m_rowTable) {
1851  ret += group->filtered.size();
1852  }
1853 
1854  return ret;
1855 }
1856 
1858 {
1859  // @todo Make this faster
1860 
1861  bool doHide = false;
1862  CodeCompletionModel *hideModel = nullptr;
1863 
1864  for (Group *group : qAsConst(m_rowTable)) {
1865  for (const Item &item : qAsConst(group->filtered)) {
1866  if (item.haveExactMatch()) {
1868  bool hide = false;
1869  if (!iface3) {
1870  hide = true;
1871  }
1872  if (iface3 && iface3->matchingItem(item.sourceRow().second) == KTextEditor::CodeCompletionModelControllerInterface::HideListIfAutomaticInvocation) {
1873  hide = true;
1874  }
1875  if (hide) {
1876  doHide = true;
1877  hideModel = item.sourceRow().first;
1878  }
1879  }
1880  }
1881  }
1882 
1883  if (doHide) {
1884  // Check if all other visible items are from the same model
1885  for (Group *group : qAsConst(m_rowTable)) {
1886  for (const Item &item : qAsConst(group->filtered)) {
1887  if (item.sourceRow().first != hideModel) {
1888  return false;
1889  }
1890  }
1891  }
1892  }
1893 
1894  return doHide;
1895 }
1896 
1897 static inline QChar toLowerIfInsensitive(QChar c, Qt::CaseSensitivity caseSensitive)
1898 {
1899  return (caseSensitive == Qt::CaseInsensitive) ? c.toLower() : c;
1900 }
1901 
1902 static inline bool matchesAbbreviationHelper(const QString &word, const QString &typed, const QVarLengthArray<int, 32> &offsets, Qt::CaseSensitivity caseSensitive, int &depth, int atWord = -1, int i = 0)
1903 {
1904  int atLetter = 1;
1905  for (; i < typed.size(); i++) {
1906  const QChar c = toLowerIfInsensitive(typed.at(i), caseSensitive);
1907  bool haveNextWord = offsets.size() > atWord + 1;
1908  bool canCompare = atWord != -1 && word.size() > offsets.at(atWord) + atLetter;
1909  if (canCompare && c == toLowerIfInsensitive(word.at(offsets.at(atWord) + atLetter), caseSensitive)) {
1910  // the typed letter matches a letter after the current word beginning
1911  if (!haveNextWord || c != toLowerIfInsensitive(word.at(offsets.at(atWord + 1)), caseSensitive)) {
1912  // good, simple case, no conflict
1913  atLetter += 1;
1914  continue;
1915  }
1916  // For maliciously crafted data, the code used here theoretically can have very high
1917  // complexity. Thus ensure we don't run into this case, by limiting the amount of branches
1918  // we walk through to 128.
1919  depth++;
1920  if (depth > 128) {
1921  return false;
1922  }
1923  // the letter matches both the next word beginning and the next character in the word
1924  if (haveNextWord && matchesAbbreviationHelper(word, typed, offsets, caseSensitive, depth, atWord + 1, i + 1)) {
1925  // resolving the conflict by taking the next word's first character worked, fine
1926  return true;
1927  }
1928  // otherwise, continue by taking the next letter in the current word.
1929  atLetter += 1;
1930  continue;
1931  } else if (haveNextWord && c == toLowerIfInsensitive(word.at(offsets.at(atWord + 1)), caseSensitive)) {
1932  // the typed letter matches the next word beginning
1933  atWord++;
1934  atLetter = 1;
1935  continue;
1936  }
1937  // no match
1938  return false;
1939  }
1940  // all characters of the typed word were matched
1941  return true;
1942 }
1943 
1944 bool KateCompletionModel::matchesAbbreviation(const QString &word, const QString &typed, Qt::CaseSensitivity caseSensitive)
1945 {
1946  // A mismatch is very likely for random even for the first letter,
1947  // thus this optimization makes sense.
1948  if (toLowerIfInsensitive(word.at(0), caseSensitive) != toLowerIfInsensitive(typed.at(0), caseSensitive)) {
1949  return false;
1950  }
1951 
1952  // First, check if all letters are contained in the word in the right order.
1953  int atLetter = 0;
1954  for (const QChar c : typed) {
1955  while (toLowerIfInsensitive(c, caseSensitive) != toLowerIfInsensitive(word.at(atLetter), caseSensitive)) {
1956  atLetter += 1;
1957  if (atLetter >= word.size()) {
1958  return false;
1959  }
1960  }
1961  }
1962 
1963  bool haveUnderscore = true;
1964  QVarLengthArray<int, 32> offsets;
1965  // We want to make "KComplM" match "KateCompletionModel"; this means we need
1966  // to allow parts of the typed text to be not part of the actual abbreviation,
1967  // which consists only of the uppercased / underscored letters (so "KCM" in this case).
1968  // However it might be ambiguous whether a letter is part of such a word or part of
1969  // the following abbreviation, so we need to find all possible word offsets first,
1970  // then compare.
1971  for (int i = 0; i < word.size(); i++) {
1972  const QChar c = word.at(i);
1973  if (c == QLatin1Char('_')) {
1974  haveUnderscore = true;
1975  } else if (haveUnderscore || c.isUpper()) {
1976  offsets.append(i);
1977  haveUnderscore = false;
1978  }
1979  }
1980  int depth = 0;
1981  return matchesAbbreviationHelper(word, typed, offsets, caseSensitive, depth);
1982 }
1983 
1984 static inline bool containsAtWordBeginning(const QString &word, const QString &typed, Qt::CaseSensitivity caseSensitive)
1985 {
1986  for (int i = 1; i < word.size(); i++) {
1987  // The current position is a word beginning if the previous character was an underscore
1988  // or if the current character is uppercase. Subsequent uppercase characters do not count,
1989  // to handle the special case of UPPER_CASE_VARS properly.
1990  const QChar c = word.at(i);
1991  const QChar prev = word.at(i - 1);
1992  if (!(prev == QLatin1Char('_') || (c.isUpper() && !prev.isUpper()))) {
1993  continue;
1994  }
1995  if (word.midRef(i).startsWith(typed, caseSensitive)) {
1996  return true;
1997  }
1998  }
1999  return false;
2000 }
2001 
2002 KateCompletionModel::Item::MatchType KateCompletionModel::Item::match()
2003 {
2004  QString match = model->currentCompletion(m_sourceRow.first);
2005 
2006  m_haveExactMatch = false;
2007 
2008  // Hehe, everything matches nothing! (ie. everything matches a blank string)
2009  if (match.isEmpty()) {
2010  return PerfectMatch;
2011  }
2012  if (m_nameColumn.isEmpty()) {
2013  return NoMatch;
2014  }
2015 
2016  matchCompletion = (m_nameColumn.startsWith(match, model->matchCaseSensitivity()) ? StartsWithMatch : NoMatch);
2017  if (matchCompletion == NoMatch) {
2018  // if no match, try for "contains"
2019  // Only match when the occurrence is at a "word" beginning, marked by
2020  // an underscore or a capital. So Foo matches BarFoo and Bar_Foo, but not barfoo.
2021  // Starting at 1 saves looking at the beginning of the word, that was already checked above.
2022  if (containsAtWordBeginning(m_nameColumn, match, model->matchCaseSensitivity())) {
2023  matchCompletion = ContainsMatch;
2024  }
2025  }
2026 
2027  if (matchCompletion == NoMatch && !m_nameColumn.isEmpty() && !match.isEmpty()) {
2028  // if still no match, try abbreviation matching
2029  if (matchesAbbreviation(m_nameColumn, match, model->matchCaseSensitivity())) {
2030  matchCompletion = AbbreviationMatch;
2031  }
2032  }
2033 
2034  if (matchCompletion && match.length() == m_nameColumn.length()) {
2035  if (model->matchCaseSensitivity() == Qt::CaseInsensitive && model->exactMatchCaseSensitivity() == Qt::CaseSensitive && !m_nameColumn.startsWith(match, Qt::CaseSensitive)) {
2036  return matchCompletion;
2037  }
2038  matchCompletion = PerfectMatch;
2039  m_haveExactMatch = true;
2040  }
2041 
2042  return matchCompletion;
2043 }
2044 
2045 QString KateCompletionModel::propertyName(KTextEditor::CodeCompletionModel::CompletionProperty property)
2046 {
2047  switch (property) {
2048  case CodeCompletionModel::Public:
2049  return i18n("Public");
2050 
2051  case CodeCompletionModel::Protected:
2052  return i18n("Protected");
2053 
2054  case CodeCompletionModel::Private:
2055  return i18n("Private");
2056 
2057  case CodeCompletionModel::Static:
2058  return i18n("Static");
2059 
2060  case CodeCompletionModel::Const:
2061  return i18n("Constant");
2062 
2063  case CodeCompletionModel::Namespace:
2064  return i18n("Namespace");
2065 
2066  case CodeCompletionModel::Class:
2067  return i18n("Class");
2068 
2069  case CodeCompletionModel::Struct:
2070  return i18n("Struct");
2071 
2072  case CodeCompletionModel::Union:
2073  return i18n("Union");
2074 
2075  case CodeCompletionModel::Function:
2076  return i18n("Function");
2077 
2078  case CodeCompletionModel::Variable:
2079  return i18n("Variable");
2080 
2081  case CodeCompletionModel::Enum:
2082  return i18n("Enumeration");
2083 
2084  case CodeCompletionModel::Template:
2085  return i18n("Template");
2086 
2087  case CodeCompletionModel::Virtual:
2088  return i18n("Virtual");
2089 
2090  case CodeCompletionModel::Override:
2091  return i18n("Override");
2092 
2093  case CodeCompletionModel::Inline:
2094  return i18n("Inline");
2095 
2096  case CodeCompletionModel::Friend:
2097  return i18n("Friend");
2098 
2099  case CodeCompletionModel::Signal:
2100  return i18n("Signal");
2101 
2102  case CodeCompletionModel::Slot:
2103  return i18n("Slot");
2104 
2105  case CodeCompletionModel::LocalScope:
2106  return i18n("Local Scope");
2107 
2108  case CodeCompletionModel::NamespaceScope:
2109  return i18n("Namespace Scope");
2110 
2111  case CodeCompletionModel::GlobalScope:
2112  return i18n("Global Scope");
2113 
2114  default:
2115  return i18n("Unknown Property");
2116  }
2117 }
2118 
2119 bool KateCompletionModel::Item::isVisible() const
2120 {
2121  return matchCompletion && matchFilters;
2122 }
2123 
2124 bool KateCompletionModel::Item::isFiltered() const
2125 {
2126  return !matchFilters;
2127 }
2128 
2129 bool KateCompletionModel::Item::isMatching() const
2130 {
2131  return matchFilters;
2132 }
2133 
2134 const KateCompletionModel::ModelRow &KateCompletionModel::Item::sourceRow() const
2135 {
2136  return m_sourceRow;
2137 }
2138 
2139 QString KateCompletionModel::currentCompletion(KTextEditor::CodeCompletionModel *model) const
2140 {
2141  return m_currentMatch.value(model);
2142 }
2143 
2144 Qt::CaseSensitivity KateCompletionModel::matchCaseSensitivity() const
2145 {
2146  return m_matchCaseSensitivity;
2147 }
2148 
2149 Qt::CaseSensitivity KateCompletionModel::exactMatchCaseSensitivity() const
2150 {
2151  return m_exactMatchCaseSensitivity;
2152 }
2153 
2154 void KateCompletionModel::addCompletionModel(KTextEditor::CodeCompletionModel *model)
2155 {
2156  if (m_completionModels.contains(model)) {
2157  return;
2158  }
2159 
2160  m_completionModels.append(model);
2161 
2162  connect(model, SIGNAL(rowsInserted(QModelIndex, int, int)), SLOT(slotRowsInserted(QModelIndex, int, int)));
2163  connect(model, SIGNAL(rowsRemoved(QModelIndex, int, int)), SLOT(slotRowsRemoved(QModelIndex, int, int)));
2164  connect(model, SIGNAL(modelReset()), SLOT(slotModelReset()));
2165 
2166  // This performs the reset
2167  createGroups();
2168 }
2169 
2170 void KateCompletionModel::setCompletionModel(KTextEditor::CodeCompletionModel *model)
2171 {
2172  clearCompletionModels();
2173  addCompletionModel(model);
2174 }
2175 
2176 void KateCompletionModel::setCompletionModels(const QList<KTextEditor::CodeCompletionModel *> &models)
2177 {
2178  // if (m_completionModels == models)
2179  // return;
2180 
2181  clearCompletionModels();
2182 
2183  m_completionModels = models;
2184 
2185  for (KTextEditor::CodeCompletionModel *model : models) {
2186  connect(model, SIGNAL(rowsInserted(QModelIndex, int, int)), SLOT(slotRowsInserted(QModelIndex, int, int)));
2187  connect(model, SIGNAL(rowsRemoved(QModelIndex, int, int)), SLOT(slotRowsRemoved(QModelIndex, int, int)));
2188  connect(model, SIGNAL(modelReset()), SLOT(slotModelReset()));
2189  }
2190 
2191  // This performs the reset
2192  createGroups();
2193 }
2194 
2195 QList<KTextEditor::CodeCompletionModel *> KateCompletionModel::completionModels() const
2196 {
2197  return m_completionModels;
2198 }
2199 
2200 void KateCompletionModel::removeCompletionModel(CodeCompletionModel *model)
2201 {
2202  if (!model || !m_completionModels.contains(model)) {
2203  return;
2204  }
2205 
2206  beginResetModel();
2207  m_currentMatch.remove(model);
2208 
2209  clearGroups();
2210 
2211  model->disconnect(this);
2212 
2213  m_completionModels.removeAll(model);
2214  endResetModel();
2215 
2216  if (!m_completionModels.isEmpty()) {
2217  // This performs the reset
2218  createGroups();
2219  }
2220 }
2221 
2222 void KateCompletionModel::makeGroupItemsUnique(bool onlyFiltered)
2223 {
2224  struct FilterItems {
2225  FilterItems(KateCompletionModel &model, const QVector<KTextEditor::CodeCompletionModel *> &needShadowing)
2226  : m_model(model)
2227  , m_needShadowing(needShadowing)
2228  {
2229  }
2230 
2232  KateCompletionModel &m_model;
2233  const QVector<KTextEditor::CodeCompletionModel *> m_needShadowing;
2234 
2235  void filter(QList<Item> &items)
2236  {
2237  QList<Item> temp;
2238  for (const Item &item : qAsConst(items)) {
2240  if (it != had.constEnd() && *it != item.sourceRow().first && m_needShadowing.contains(item.sourceRow().first)) {
2241  continue;
2242  }
2243  had.insert(item.name(), item.sourceRow().first);
2244  temp.push_back(item);
2245  }
2246  items = temp;
2247  }
2248 
2249  void filter(Group *group, bool onlyFiltered)
2250  {
2251  if (group->prefilter.size() == group->filtered.size()) {
2252  // Filter only once
2253  filter(group->filtered);
2254  if (!onlyFiltered) {
2255  group->prefilter = group->filtered;
2256  }
2257  } else {
2258  // Must filter twice
2259  filter(group->filtered);
2260  if (!onlyFiltered) {
2261  filter(group->prefilter);
2262  }
2263  }
2264 
2265  if (group->filtered.isEmpty()) {
2266  m_model.hideOrShowGroup(group);
2267  }
2268  }
2269  };
2270 
2272  for (KTextEditor::CodeCompletionModel *model : qAsConst(m_completionModels)) {
2274  if (v4 && v4->shouldHideItemsWithEqualNames()) {
2275  needShadowing.push_back(model);
2276  }
2277  }
2278 
2279  if (needShadowing.isEmpty()) {
2280  return;
2281  }
2282 
2283  FilterItems filter(*this, needShadowing);
2284 
2285  filter.filter(m_ungrouped, onlyFiltered);
2286 
2287  for (Group *group : qAsConst(m_rowTable)) {
2288  filter.filter(group, onlyFiltered);
2289  }
2290 }
2291 
2292 // Updates the best-matches group
2293 void KateCompletionModel::updateBestMatches()
2294 {
2295  int maxMatches = 300; // We cannot do too many operations here, because they are all executed whenever a character is added. Would be nice if we could split the operations up somewhat using a timer.
2296 
2297  m_updateBestMatchesTimer->stop();
2298  // Maps match-qualities to ModelRows paired together with the BestMatchesCount returned by the items.
2299  typedef QMultiMap<int, QPair<int, ModelRow>> BestMatchMap;
2300  BestMatchMap matches;
2301 
2302  if (!hasGroups()) {
2303  // If there is no grouping, just change the order of the items, moving the best matching ones to the front
2304  QMultiMap<int, int> rowsForQuality;
2305 
2306  int row = 0;
2307  for (const Item &item : qAsConst(m_ungrouped->filtered)) {
2308  ModelRow source = item.sourceRow();
2309 
2310  QVariant v = source.second.data(CodeCompletionModel::BestMatchesCount);
2311 
2312  if (v.type() == QVariant::Int && v.toInt() > 0) {
2313  int quality = contextMatchQuality(source);
2314  if (quality > 0) {
2315  rowsForQuality.insert(quality, row);
2316  }
2317  }
2318 
2319  ++row;
2320  --maxMatches;
2321  if (maxMatches < 0) {
2322  break;
2323  }
2324  }
2325 
2326  if (!rowsForQuality.isEmpty()) {
2327  // Rewrite m_ungrouped->filtered in a new order
2328  QSet<int> movedToFront;
2329  QList<Item> newFiltered;
2330  for (QMultiMap<int, int>::const_iterator it = rowsForQuality.constBegin(); it != rowsForQuality.constEnd(); ++it) {
2331  newFiltered.prepend(m_ungrouped->filtered[it.value()]);
2332  movedToFront.insert(it.value());
2333  }
2334 
2335  {
2336  int size = m_ungrouped->filtered.size();
2337  for (int a = 0; a < size; ++a)
2338  if (!movedToFront.contains(a)) {
2339  newFiltered.append(m_ungrouped->filtered[a]);
2340  }
2341  }
2342  m_ungrouped->filtered = newFiltered;
2343  }
2344  return;
2345  }
2346 
2348  for (Group *g : qAsConst(m_rowTable)) {
2349  if (g == m_bestMatches) {
2350  continue;
2351  }
2352  for (int a = 0; a < g->filtered.size(); a++) {
2353  ModelRow source = g->filtered[a].sourceRow();
2354 
2355  QVariant v = source.second.data(CodeCompletionModel::BestMatchesCount);
2356 
2357  if (v.type() == QVariant::Int && v.toInt() > 0) {
2358  // Return the best match with any of the argument-hints
2359 
2360  int quality = contextMatchQuality(source);
2361  if (quality > 0) {
2362  matches.insert(quality, qMakePair(v.toInt(), g->filtered[a].sourceRow()));
2363  }
2364  --maxMatches;
2365  }
2366 
2367  if (maxMatches < 0) {
2368  break;
2369  }
2370  }
2371  if (maxMatches < 0) {
2372  break;
2373  }
2374  }
2375 
2376  // Now choose how many of the matches will be taken. This is done with the rule:
2377  // The count of shown best-matches should equal the average count of their BestMatchesCounts
2378  int cnt = 0;
2379  int matchesSum = 0;
2380  BestMatchMap::const_iterator it = matches.constEnd();
2381  while (it != matches.constBegin()) {
2382  --it;
2383  ++cnt;
2384  matchesSum += (*it).first;
2385  if (cnt > matchesSum / cnt) {
2386  break;
2387  }
2388  }
2389 
2390  m_bestMatches->filtered.clear();
2391 
2392  it = matches.constEnd();
2393 
2394  while (it != matches.constBegin() && cnt > 0) {
2395  --it;
2396  --cnt;
2397 
2398  m_bestMatches->filtered.append(Item(true, this, HierarchicalModelHandler((*it).second.first), (*it).second));
2399  }
2400 
2401  hideOrShowGroup(m_bestMatches);
2402 }
2403 
2405 {
2408  int rc = widget()->argumentHintModel()->rowCount(QModelIndex());
2409  if (rc == 0) {
2410  return;
2411  }
2412 
2413  // For now, simply update the whole column 0
2414  QModelIndex start = widget()->argumentHintModel()->index(0, 0);
2415  QModelIndex end = widget()->argumentHintModel()->index(rc - 1, 0);
2416 
2417  widget()->argumentHintModel()->emitDataChanged(start, end);
2418 }
2419 
2420 void KateCompletionModel::clearCompletionModels()
2421 {
2422  if (m_completionModels.isEmpty()) {
2423  return;
2424  }
2425 
2426  beginResetModel();
2427  for (CodeCompletionModel *model : qAsConst(m_completionModels)) {
2428  model->disconnect(this);
2429  }
2430 
2431  m_completionModels.clear();
2432 
2433  m_currentMatch.clear();
2434 
2435  clearGroups();
2436  endResetModel();
2437 }
Is requested before MatchQuality(..) is requested.
bool canConvert(int targetTypeId) const const
Returns the inheritance depth of the completion.
const QBrush & toolTipBase() const const
const T & at(int i) const const
virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const
Maps from an index in a source-model to the index of the item in this display-model.
virtual int rowCount(const QModelIndex &parent) const const =0
QString & append(QChar ch)
QHash::iterator insert(const Key &key, const T &value)
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const =0
void truncate(int position)
virtual void rowSelected(const QModelIndex &row)
Notifies underlying models that the item was selected, collapses any previous partially expanded line...
QString commonPrefix(QModelIndex selectedIndex) const
Returns a common prefix for all current visible completion entries If there is no common prefix...
Allows an item to provide custom highlighting.
void push_back(const T &value)
void append(const T &t)
virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const
Maps from this display-model into the appropriate source code-completion model.
int length() const const
void reserve(int alloc)
QMap::const_iterator constBegin() const const
int size() const const
QHash::const_iterator constFind(const Key &key) const const
This class has the responsibility for filtering, sorting, and manipulating code completion data provi...
QSet::iterator insert(const T &value)
QWidget * widget() override
const QBrush & toolTipText() const const
Icon representing the type of completion.
AlignRight
int size() const const
bool isNull() const const
int contextMatchQuality(const QModelIndex &index) const override
const QColor & color() const const
void setBold(bool enable)
bool isValid() const const
int count(const T &value) const const
bool contains(const T &value) const const
void append(const T &value)
This is the code completion&#39;s main widget, and also contains the core interface logic.
void rowSelected(const QModelIndex &row) override
Notifies underlying models that the item was selected, collapses any previous partially expanded line...
QString & insert(int position, QChar ch)
QHash::const_iterator constEnd() const const
bool indexIsItem(const QModelIndex &index) const override
Should return true if the given row should be painted like a contained item(as opposed to label-rows ...
int toInt(bool *ok) const const
QPalette palette()
CaseSensitivity
DisplayRole
bool isEmpty() const const
QMap::const_iterator constEnd() const const
bool isUpper() const const
void beginRemoveRows(const QModelIndex &parent, int first, int last)
int row() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
void * internalPointer() const const
typename QMap< Key, T >::iterator insert(const Key &key, const T &value)
T & first()
Return a nonzero value here to enforce sorting the item at the end of the list.
QModelIndex parent() const const
The KTextEditor namespace contains all the public API that is required to use the KTextEditor compone...
If requested, your model should try to determine whether the completion in question is a suitable mat...
This will be requested for each item to ask whether it should be included in computing a best-matches...
Define which highlighting method will be used:
ExtraItemDataRoles
Meta information is passed through extra {Qt::ItemDataRole}s.
QStringRef midRef(int position, int n) const const
QChar toLower() const const
bool contains(const T &value) const const
void beginInsertRows(const QModelIndex &parent, int first, int last)
QString i18n(const char *text, const TYPE &arg...)
The model should return a set of CompletionProperties.
Using this Role, it is possible to greatly optimize the time needed to process very long completion-l...
virtual MatchReaction matchingItem(const QModelIndex &matched)
Called whenever an item in the completion-list perfectly matches the current filter text...
const QAbstractItemModel * model() const const
QString mid(int position, int n) const const
bool shouldMatchHideCompletionList() const
Returns whether one of the filtered items exactly matches its completion string.
QVariant data(int role) const const
SortOrder
bool isEmpty() const const
QModelIndex sibling(int row, int column) const const
const QChar at(int position) const const
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
Does not request data from index, this only returns local data like highlighting for expanded rows an...
int column() const const
int length() const const
void push_back(const T &value)
bool isValid() const const
void prepend(const T &value)
virtual bool shouldHideItemsWithEqualNames() const
When multiple completion models are used at the same time, it may happen that multiple models add ite...
Is this completion-item an argument-hint? The model should return an integral positive number if the ...
bool isEmpty() const const
QVariant::Type type() const const
ItemType
QObject * parent() const const
int compare(const QString &other, Qt::CaseSensitivity cs) const const
QString toString() const const
int size() const const
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
Does not request data from index, this only returns local data like highlighting for expanded rows an...
bool startsWith(QStringView str, Qt::CaseSensitivity cs) const const
Cares about expanding/un-expanding items in a tree-view together with ExpandingDelegate.
typedef ItemFlags
An item model for providing code completion, and meta information for enhanced presentation.
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Thu Jul 9 2020 22:56:52 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.