1/*
2 SPDX-FileCopyrightText: 2005-2006 Hamish Rodda <rodda@kde.org>
3 SPDX-FileCopyrightText: 2007-2008 David Nolden <david.nolden.kdevelop@art-master.de>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#ifndef KATECOMPLETIONMODEL_H
9#define KATECOMPLETIONMODEL_H
10
11#include <QAbstractProxyModel>
12#include <QList>
13#include <QPair>
14
15#include <ktexteditor/codecompletionmodel.h>
16
17#include "expandingtree/expandingwidgetmodel.h"
18#include <ktexteditor_export.h>
19
20#include <set>
21
22class KateCompletionWidget;
23class KateArgumentHintModel;
24namespace KTextEditor
25{
26class ViewPrivate;
27}
28class QWidget;
29class QTextEdit;
30class QTimer;
31class HierarchicalModelHandler;
32
33/**
34 * This class has the responsibility for filtering, sorting, and manipulating
35 * code completion data provided by a CodeCompletionModel.
36 *
37 * @author Hamish Rodda <rodda@kde.org>
38 */
39class KateCompletionModel : public ExpandingWidgetModel
40{
41 Q_OBJECT
42
43public:
44 enum InternalRole {
45 IsNonEmptyGroup = KTextEditor::CodeCompletionModel::LastExtraItemDataRole + 1,
46 };
47
48 explicit KateCompletionModel(KateCompletionWidget *parent = nullptr);
49 ~KateCompletionModel() override;
50
51 QList<KTextEditor::CodeCompletionModel *> completionModels() const;
52 void clearCompletionModels();
53 KTEXTEDITOR_EXPORT void addCompletionModel(KTextEditor::CodeCompletionModel *model);
54 KTEXTEDITOR_EXPORT void setCompletionModel(KTextEditor::CodeCompletionModel *model);
55 void setCompletionModels(const QList<KTextEditor::CodeCompletionModel *> &models);
56 KTEXTEDITOR_EXPORT void removeCompletionModel(KTextEditor::CodeCompletionModel *model);
57
58 KTextEditor::ViewPrivate *view() const;
59 KateCompletionWidget *widget() const;
60
61 KTEXTEDITOR_EXPORT QString currentCompletion(KTextEditor::CodeCompletionModel *model) const;
62 void setCurrentCompletion(QMap<KTextEditor::CodeCompletionModel *, QString> currentMatch);
63
64 int translateColumn(int sourceColumn) const;
65
66 /// Returns a common prefix for all current visible completion entries
67 /// If there is no common prefix, extracts the next useful prefix for the selected index
68 QString commonPrefix(QModelIndex selectedIndex) const;
69
70 void rowSelected(const QModelIndex &row) const;
71
72 bool indexIsItem(const QModelIndex &index) const override;
73
74 int columnCount(const QModelIndex &parent = QModelIndex()) const override;
75 QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
76 Qt::ItemFlags flags(const QModelIndex &index) const override;
77 bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;
78 virtual bool hasIndex(int row, int column, const QModelIndex &parent = QModelIndex()) const;
79 QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
80
81 // Disabled in case of bugs, reenable once fully debugged.
82 // virtual QMap<int, QVariant> itemData ( const QModelIndex & index ) const;
83 QModelIndex parent(const QModelIndex &index) const override;
84 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
85
86 /// Maps from this display-model into the appropriate source code-completion model
87 virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const;
88
89 /// Maps from an index in a source-model to the index of the item in this display-model
90 virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const;
91
92 enum gm { ScopeType = 0x1, Scope = 0x2, AccessType = 0x4, ItemType = 0x8 };
93
94 enum { // An own property that will be used to mark the best-matches group internally
95 BestMatchesProperty = 2 * KTextEditor::CodeCompletionModel::LastProperty
96 };
97
98 Q_DECLARE_FLAGS(GroupingMethods, gm)
99
100 static const int ScopeTypeMask = 0x380000;
101 static const int AccessTypeMask = 0x7;
102 static const int ItemTypeMask = 0xfe0;
103
104 void debugStats();
105
106 /// Returns whether one of the filtered items exactly matches its completion string
107 bool shouldMatchHideCompletionList() const;
108
109 KTEXTEDITOR_EXPORT uint filteredItemCount() const;
110
111protected:
112 int contextMatchQuality(const QModelIndex &index) const override;
113
114Q_SIGNALS:
115 void expandIndex(const QModelIndex &index);
116 // Emitted whenever something has changed about the group of argument-hints
117 void argumentHintsChanged();
118
119private Q_SLOTS:
120 void slotRowsInserted(const QModelIndex &parent, int start, int end);
121 void slotRowsRemoved(const QModelIndex &parent, int start, int end);
122 void slotModelReset();
123
124 // Updates the best-matches group
125 void updateBestMatches();
126 // Makes sure that the ungrouped group contains each item only once
127 // Must only be called right after the group was created
128 void makeGroupItemsUnique(bool onlyFiltered = false);
129
130private:
131 typedef QPair<KTextEditor::CodeCompletionModel *, QModelIndex> ModelRow;
132 virtual int contextMatchQuality(const ModelRow &sourceRow) const;
133
134 QTreeView *treeView() const override;
135
136 friend class KateArgumentHintModel;
137 static ModelRow modelRowPair(const QModelIndex &index);
138
139 // Represents a source row; provides sorting method
140 class Item
141 {
142 public:
143 Item(bool doInitialMatch, KateCompletionModel *model, const HierarchicalModelHandler &handler, ModelRow sourceRow);
144
145 // Returns true if the item is not filtered and matches the current completion string
146 bool isVisible() const;
147
148 enum MatchType { NoMatch = 0, PerfectMatch, StartsWithMatch, AbbreviationMatch, ContainsMatch };
149 MatchType match(KateCompletionModel *model);
150
151 const ModelRow &sourceRow() const;
152
153 // Sorting operator
154 bool lessThan(KateCompletionModel *model, const Item &rhs) const;
155
156 bool haveExactMatch() const
157 {
158 return m_haveExactMatch;
159 }
160
161 QString name() const
162 {
163 return m_nameColumn;
164 }
165
166 private:
167 ModelRow m_sourceRow;
168
169 QString m_nameColumn;
170
171 int inheritanceDepth;
172
173 // True when currently matching completion string
174 MatchType matchCompletion;
175 bool m_haveExactMatch;
176 bool m_unimportant;
177 };
178
179public:
180 // Grouping and sorting of rows
181 class Group
182 {
183 public:
184 explicit Group(const QString &title, int attribute, KateCompletionModel *model);
185
186 void addItem(const Item &i, bool notifyModel = false);
187 /// Removes the item specified by \a row. Returns true if a change was made to rows.
188 bool removeItem(const ModelRow &row);
189 void resort();
190 void clear();
191 // Returns whether this group should be ordered before other
192 bool orderBefore(Group *other) const;
193 // Returns a number that can be used for ordering
194 int orderNumber() const;
195
196 /// Returns the row in the this group's filtered list of the given model-row in a source-model
197 ///-1 if the item is not in the filtered list
198 ///@todo Implement an efficient way of doing this map, that does _not_ iterate over all items!
199 int rowOf(const ModelRow &item)
200 {
201 for (int a = 0; a < (int)filtered.size(); ++a) {
202 if (filtered[a].sourceRow() == item) {
203 return a;
204 }
205 }
206 return -1;
207 }
208
209 KateCompletionModel *model;
210 int attribute;
211 QString title, scope;
212 std::vector<Item> filtered;
213 std::vector<Item> prefilter;
214 bool isEmpty;
215 //-1 if none was set
216 int customSortingKey;
217 };
218
219 typedef std::set<Group *> GroupSet;
220
221 bool hasGroups() const
222 {
223 // qCDebug(LOG_KTE) << "m_groupHash.size()"<<m_groupHash.size();
224 // qCDebug(LOG_KTE) << "m_rowTable.count()"<<m_rowTable.count();
225 return m_hasGroups;
226 }
227
228private:
229 QString commonPrefixInternal(const QString &forcePrefix) const;
230 /// @note performs model reset
231 void createGroups();
232 /// Creates all sub-items of index i, or the item corresponding to index i. Returns the affected groups.
233 /// i must be an index in the source model
234 GroupSet createItems(const HierarchicalModelHandler &, const QModelIndex &i, bool notifyModel = false);
235 /// Deletes all sub-items of index i, or the item corresponding to index i. Returns the affected groups.
236 /// i must be an index in the source model
237 GroupSet deleteItems(const QModelIndex &i);
238 Group *createItem(const HierarchicalModelHandler &, const QModelIndex &i, bool notifyModel = false);
239 /// @note Make sure you're in a {begin,end}ResetModel block when calling this!
240 void clearGroups();
241 void hideOrShowGroup(Group *g, bool notifyModel = false);
242 /// When forceGrouping is enabled, all given attributes will be used for grouping, regardless of the completion settings.
243 Group *fetchGroup(int attribute, bool forceGrouping = false);
244 // If this returns nonzero on an index, the index is the header of the returned group
245 Group *groupForIndex(const QModelIndex &index) const;
246 inline Group *groupOfParent(const QModelIndex &child) const
247 {
248 return static_cast<Group *>(child.internalPointer());
249 }
250 QModelIndex indexForRow(Group *g, int row) const;
251 QModelIndex indexForGroup(Group *g) const;
252
253 enum changeTypes { Broaden, Narrow, Change };
254
255 // Returns whether the model needs to be reset
256 void changeCompletions(Group *g);
257
258 bool hasCompletionModel() const;
259
260 /// Removes attributes not used in grouping from the input \a attribute
261 int groupingAttributes(int attribute) const;
262 static int countBits(int value);
263
264 void resort();
265
266 KTEXTEDITOR_EXPORT static bool matchesAbbreviation(const QString &word, const QString &typed, int &score);
267 // exported for completion_test
268
269 bool m_hasGroups = false;
270
271 // ### Runtime state
272 // General
273 QList<KTextEditor::CodeCompletionModel *> m_completionModels;
274 QMap<KTextEditor::CodeCompletionModel *, QString> m_currentMatch;
275
276 // Column merging
277 const std::array<std::vector<int>, 3> m_columnMerges = {._M_elems: {
278 {0},
279 {1, 2, 3, 4},
280 {5},
281 }};
282
283 QTimer *m_updateBestMatchesTimer;
284
285 Group *m_ungrouped;
286 Group *m_argumentHints; // The argument-hints will be passed on to another model, to be shown in another widget
287 Group *m_bestMatches; // A temporary group used for holding the best matches of all visible items
288
289 // Storing the sorted order
290 std::vector<Group *> m_rowTable;
291 std::vector<Group *> m_emptyGroups;
292 // Quick access to each specific group (if it exists)
293 QMultiHash<int, Group *> m_groupHash;
294 // Maps custom group-names to their specific groups
295 QHash<QString, Group *> m_customGroupHash;
296
297 friend class CompletionTest;
298};
299
300Q_DECLARE_OPERATORS_FOR_FLAGS(KateCompletionModel::GroupingMethods)
301
302#endif
303

source code of ktexteditor/src/completion/katecompletionmodel.h