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 | |
22 | class KateCompletionWidget; |
23 | class KateArgumentHintModel; |
24 | namespace KTextEditor |
25 | { |
26 | class ViewPrivate; |
27 | } |
28 | class QWidget; |
29 | class QTextEdit; |
30 | class QTimer; |
31 | class 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 | */ |
39 | class KateCompletionModel : public ExpandingWidgetModel |
40 | { |
41 | Q_OBJECT |
42 | |
43 | public: |
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 { |
93 | ScopeType = 0x1, |
94 | Scope = 0x2, |
95 | AccessType = 0x4, |
96 | ItemType = 0x8 |
97 | }; |
98 | |
99 | enum { // An own property that will be used to mark the best-matches group internally |
100 | BestMatchesProperty = 2 * KTextEditor::CodeCompletionModel::LastProperty |
101 | }; |
102 | |
103 | Q_DECLARE_FLAGS(GroupingMethods, gm) |
104 | |
105 | static const int ScopeTypeMask = 0x380000; |
106 | static const int AccessTypeMask = 0x7; |
107 | static const int ItemTypeMask = 0xfe0; |
108 | |
109 | void debugStats(); |
110 | |
111 | /// Returns whether one of the filtered items exactly matches its completion string |
112 | bool shouldMatchHideCompletionList() const; |
113 | |
114 | KTEXTEDITOR_EXPORT uint filteredItemCount() const; |
115 | |
116 | protected: |
117 | int contextMatchQuality(const QModelIndex &index) const override; |
118 | |
119 | Q_SIGNALS: |
120 | void expandIndex(const QModelIndex &index); |
121 | // Emitted whenever something has changed about the group of argument-hints |
122 | void argumentHintsChanged(); |
123 | |
124 | private Q_SLOTS: |
125 | void slotRowsInserted(const QModelIndex &parent, int start, int end); |
126 | void slotRowsRemoved(const QModelIndex &parent, int start, int end); |
127 | void slotModelReset(); |
128 | |
129 | // Updates the best-matches group |
130 | void updateBestMatches(); |
131 | // Makes sure that the ungrouped group contains each item only once |
132 | // Must only be called right after the group was created |
133 | void makeGroupItemsUnique(bool onlyFiltered = false); |
134 | |
135 | private: |
136 | typedef QPair<KTextEditor::CodeCompletionModel *, QModelIndex> ModelRow; |
137 | virtual int contextMatchQuality(const ModelRow &sourceRow) const; |
138 | |
139 | QTreeView *treeView() const override; |
140 | |
141 | friend class KateArgumentHintModel; |
142 | static ModelRow modelRowPair(const QModelIndex &index); |
143 | |
144 | // Represents a source row; provides sorting method |
145 | class Item |
146 | { |
147 | public: |
148 | Item(bool doInitialMatch, KateCompletionModel *model, const HierarchicalModelHandler &handler, ModelRow sourceRow); |
149 | |
150 | // Returns true if the item is not filtered and matches the current completion string |
151 | bool isVisible() const; |
152 | |
153 | enum MatchType { |
154 | NoMatch = 0, |
155 | PerfectMatch, |
156 | StartsWithMatch, |
157 | AbbreviationMatch, |
158 | ContainsMatch |
159 | }; |
160 | MatchType match(KateCompletionModel *model); |
161 | |
162 | const ModelRow &sourceRow() const; |
163 | |
164 | // Sorting operator |
165 | bool lessThan(KateCompletionModel *model, const Item &rhs) const; |
166 | |
167 | bool haveExactMatch() const |
168 | { |
169 | return m_haveExactMatch; |
170 | } |
171 | |
172 | QString name() const |
173 | { |
174 | return m_nameColumn; |
175 | } |
176 | |
177 | private: |
178 | ModelRow m_sourceRow; |
179 | |
180 | QString m_nameColumn; |
181 | |
182 | int inheritanceDepth; |
183 | |
184 | // True when currently matching completion string |
185 | MatchType matchCompletion; |
186 | bool m_haveExactMatch; |
187 | bool m_unimportant; |
188 | }; |
189 | |
190 | public: |
191 | // Grouping and sorting of rows |
192 | class Group |
193 | { |
194 | public: |
195 | explicit Group(const QString &title, int attribute, KateCompletionModel *model); |
196 | |
197 | void addItem(const Item &i, bool notifyModel = false); |
198 | /// Removes the item specified by \a row. Returns true if a change was made to rows. |
199 | bool removeItem(const ModelRow &row); |
200 | void resort(); |
201 | void clear(); |
202 | // Returns whether this group should be ordered before other |
203 | bool orderBefore(Group *other) const; |
204 | // Returns a number that can be used for ordering |
205 | int orderNumber() const; |
206 | |
207 | /// Returns the row in the this group's filtered list of the given model-row in a source-model |
208 | ///-1 if the item is not in the filtered list |
209 | ///@todo Implement an efficient way of doing this map, that does _not_ iterate over all items! |
210 | int rowOf(const ModelRow &item) |
211 | { |
212 | for (int a = 0; a < (int)filtered.size(); ++a) { |
213 | if (filtered[a].sourceRow() == item) { |
214 | return a; |
215 | } |
216 | } |
217 | return -1; |
218 | } |
219 | |
220 | KateCompletionModel *model; |
221 | int attribute; |
222 | QString title, scope; |
223 | std::vector<Item> filtered; |
224 | std::vector<Item> prefilter; |
225 | bool isEmpty; |
226 | //-1 if none was set |
227 | int customSortingKey; |
228 | }; |
229 | |
230 | typedef std::set<Group *> GroupSet; |
231 | |
232 | bool hasGroups() const |
233 | { |
234 | // qCDebug(LOG_KTE) << "m_groupHash.size()"<<m_groupHash.size(); |
235 | // qCDebug(LOG_KTE) << "m_rowTable.count()"<<m_rowTable.count(); |
236 | return m_hasGroups; |
237 | } |
238 | |
239 | private: |
240 | QString commonPrefixInternal(const QString &forcePrefix) const; |
241 | /// @note performs model reset |
242 | void createGroups(); |
243 | /// Creates all sub-items of index i, or the item corresponding to index i. Returns the affected groups. |
244 | /// i must be an index in the source model |
245 | GroupSet createItems(const HierarchicalModelHandler &, const QModelIndex &i, bool notifyModel = false); |
246 | /// Deletes all sub-items of index i, or the item corresponding to index i. Returns the affected groups. |
247 | /// i must be an index in the source model |
248 | GroupSet deleteItems(const QModelIndex &i); |
249 | Group *createItem(const HierarchicalModelHandler &, const QModelIndex &i, bool notifyModel = false); |
250 | /// @note Make sure you're in a {begin,end}ResetModel block when calling this! |
251 | void clearGroups(); |
252 | void hideOrShowGroup(Group *g, bool notifyModel = false); |
253 | /// When forceGrouping is enabled, all given attributes will be used for grouping, regardless of the completion settings. |
254 | Group *fetchGroup(int attribute, bool forceGrouping = false); |
255 | // If this returns nonzero on an index, the index is the header of the returned group |
256 | Group *groupForIndex(const QModelIndex &index) const; |
257 | inline Group *groupOfParent(const QModelIndex &child) const |
258 | { |
259 | return static_cast<Group *>(child.internalPointer()); |
260 | } |
261 | QModelIndex indexForRow(Group *g, int row) const; |
262 | QModelIndex indexForGroup(Group *g) const; |
263 | |
264 | enum changeTypes { |
265 | Broaden, |
266 | Narrow, |
267 | Change |
268 | }; |
269 | |
270 | // Returns whether the model needs to be reset |
271 | void changeCompletions(Group *g); |
272 | |
273 | bool hasCompletionModel() const; |
274 | |
275 | /// Removes attributes not used in grouping from the input \a attribute |
276 | int groupingAttributes(int attribute) const; |
277 | static int countBits(int value); |
278 | |
279 | void resort(); |
280 | |
281 | KTEXTEDITOR_EXPORT static bool matchesAbbreviation(const QString &word, const QString &typed, int &score); |
282 | // exported for completion_test |
283 | |
284 | bool m_hasGroups = false; |
285 | |
286 | // ### Runtime state |
287 | // General |
288 | QList<KTextEditor::CodeCompletionModel *> m_completionModels; |
289 | QMap<KTextEditor::CodeCompletionModel *, QString> m_currentMatch; |
290 | |
291 | // Column merging |
292 | const std::array<std::vector<int>, 3> m_columnMerges = {._M_elems: { |
293 | {0}, |
294 | {1, 2, 3, 4}, |
295 | {5}, |
296 | }}; |
297 | |
298 | QTimer *m_updateBestMatchesTimer; |
299 | |
300 | Group *m_ungrouped; |
301 | Group *m_argumentHints; // The argument-hints will be passed on to another model, to be shown in another widget |
302 | Group *m_bestMatches; // A temporary group used for holding the best matches of all visible items |
303 | |
304 | // Storing the sorted order |
305 | std::vector<Group *> m_rowTable; |
306 | std::vector<Group *> m_emptyGroups; |
307 | // Quick access to each specific group (if it exists) |
308 | QMultiHash<int, Group *> m_groupHash; |
309 | // Maps custom group-names to their specific groups |
310 | QHash<QString, Group *> m_customGroupHash; |
311 | |
312 | friend class CompletionTest; |
313 | }; |
314 | |
315 | Q_DECLARE_OPERATORS_FOR_FLAGS(KateCompletionModel::GroupingMethods) |
316 | |
317 | #endif |
318 | |